<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; IntelliJ</title>
	<atom:link href="https://blog.gmem.cc/tag/intellij/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Sun, 12 Apr 2026 02:07:19 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Protocol Buffers初探</title>
		<link>https://blog.gmem.cc/intro-to-protocol-buffers</link>
		<comments>https://blog.gmem.cc/intro-to-protocol-buffers#comments</comments>
		<pubDate>Wed, 07 Feb 2018 14:51:35 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=18167</guid>
		<description><![CDATA[<p>简介 ProtoBuf是Google提出的语言中立、平台无关、可扩展的数据序列化协议。比起XML、JSON等格式它更小、更简单、更快。 使用ProtoBuf，你只需要定义消息结构一次，就可以自动生成各种语言的代码来读写消息结构。 编译器 你需要ProtoBuf编译器，才能把消息定义文件编译为各种语言的源代码。执行下面的命令安装编译器： [crayon-69dc3b0cacb0c258607120/] 按照上述命令安装ProtoBuf后，路径/usr/include/google/protobuf下会包含一些proto文件。 使用编译器 上述构建步骤完成后，你可以使用protoc命令来编译.proto文件，产生特定编程语言的代码： [crayon-69dc3b0cacb11943161569/] 消息定义 ProtoBuf在.proto文件中定义消息结构或者RPC服务。 消息结构 [crayon-69dc3b0cacb14149914266/] 服务  你可以在.proto中定义一个RPC服务： [crayon-69dc3b0cacb17838364773/] ProtoBuf编译器可以生成服务的（指定语言的）接口代码和桩代码。 选项 你可以在.proto文件中声明一系列的选项，这些选项不会从根本上改变消息结构或者服务语义，但是会在特定上下文中影响编译器的行为： [crayon-69dc3b0cacb19390833527/] 编译 <a class="read-more" href="https://blog.gmem.cc/intro-to-protocol-buffers">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/intro-to-protocol-buffers">Protocol Buffers初探</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>ProtoBuf是Google提出的语言中立、平台无关、可扩展的数据序列化协议。比起XML、JSON等格式它更小、更简单、更快。</p>
<p>使用ProtoBuf，你只需要定义消息结构一次，就可以自动生成各种语言的代码来读写消息结构。</p>
<div class="blog_h1"><span class="graybg">编译器</span></div>
<p>你需要ProtoBuf编译器，才能把消息定义文件编译为各种语言的源代码。执行下面的命令安装编译器：</p>
<pre class="crayon-plain-tag">git clone https://github.com/google/protobuf.git
cd protobuf/
./autogen.sh
./configure --prefix=/usr
make &amp;&amp; sudo make install
sudo ldconfig</pre>
<p>按照上述命令安装ProtoBuf后，路径/usr/include/google/protobuf下会包含一些proto文件。</p>
<div class="blog_h2"><span class="graybg">使用编译器</span></div>
<p>上述构建步骤完成后，你可以使用protoc命令来编译.proto文件，产生特定编程语言的代码：</p>
<pre class="crayon-plain-tag">protoc 
    --proto_path=IMPORT_PATH     # .proto文件中的import指令的搜索路径，默认当前目录
    # 不同语言代码的输出目录
    --cpp_out=DST_DIR 
    --java_out=DST_DIR 
    --python_out=DST_DIR 
    --go_out=DST_DIR 
    --ruby_out=DST_DIR 
    --javanano_out=DST_DIR 
    --objc_out=DST_DIR 
    --csharp_out=DST_DIR 
    # 待编译的proto文件
    path/to/file.proto</pre>
<div class="blog_h1"><span class="graybg">消息定义</span></div>
<p>ProtoBuf在.proto文件中定义消息结构或者RPC服务。</p>
<div class="blog_h2"><span class="graybg">消息结构</span></div>
<pre class="crayon-plain-tag">/* ProtoBuf版本 */
syntax = "proto3";
// 导入其它消息定义文件
import "google/protobuf/timestamp.proto";
// 包名，默认对应Go的包名
package gmem;

// 定义消息，消息是一系列字段的聚合
message User {
    // 保留标签和字段名。这些是以前曾经使用，但是废弃的字段。为了保证新旧定义的兼容性，废弃的字段应该不再使用
    reserved 15, 9 to 11;
    reserved "foo", "bar";

    // 支持的数据类型：
    // bool  float double string bytes
    // 变长整数：int32 int64 uint32 uint64 sint32 sint64
    // 固定长度整数：fixed32 fixed64 sfixed32 sfixed64

    // 等于号的后面的数字必须是唯一的，作为串行化后字段的标签。1-15号字段比起16+字段串行化后少一个字节
    string stringType = 1; // A string must always contain UTF-8 encoded or 7-bit ASCII text. Default value = ""

    // Number Types, Default Value = 0
    int32 int32Type = 2; // Uses Variable Length Encoding. Inefficient For Negative Numbers, Instead Use sint32.
    int64 int64Type = 3; // Uses Variable Length Encoding. Inefficient For Negative Numbers, Instead Use sint64.
    uint32 uInt32Type = 4; // Uses Variable Length Encoding
    uint64 uInt64Type = 5; // Uses Variable Length Encoding
    sint32 sInt32Type = 6; // Uses Variable Length Encoding. They are efficient in encoding for negative numbers.
                           // Use this instead of int32 for negative numbers
    sint64 sInt64Type = 7; // Uses Variable Length Encoding. They are efficient in encoding for negative numbers.
    // Use this instead of int64 for negative numbers.

    fixed32 fixed32Type = 8; // Always four bytes. More efficient than uint32 if values are often greater than 2^28.
    fixed64 fixed64Type = 9; // Always eight bytes. More efficient than uint64 if values are often greater than 2^56

    sfixed32 sfixed32Type = 10; // Always four bytes.
    sfixed64 sfixed64Type = 11; // Always Eight bytes.

    bool boolType = 12; // Boolean Type. Default Value = false

    bytes bytesType = 13; // May contain any arbitrary sequence of bytes. Default Value = Empty Bytes

    double doubleType = 14;
    float floatType = 15;


    // 消息内部可以定义枚举，枚举本身不是字段
    enum TelType {
        MOBILE = 0;  // Tag 0 is always used as default in case of enum
        HOME = 1;
        WORK = 2;
        OTHER = 3;
    }
    // 可以内嵌消息定义，消息定义本身不是字段
    message TelNum {
        string number = 1;
        TelType type = 2;
    }
    // 如果声明repeated则字段可以重复0-N次。字段的顺序在串行时被保留
    repeated TelNum phones = 3;
    repeated string aliases = 4;

    // 多个字段，但是每个消息只会出现其中一个
    oneof version {
        string version = 5;
        int32 apiversion = 6;
    }
    // 映射格式支持，这类字段不能是repeated
    map&lt;string, User&gt; relatedUsers = 7;
    
    // ProtoBuf 3提供的字段类型
    google.protobuf.Timestamp dob = 8;
}

// 在Message 中可以引用其它消息类型
message Person {
    string fname = 1;
    string sname = 2;
}

message City {
    Person p = 1;
}

// Message定义可以是内嵌的
message NestedMessages {
    message FirstLevelNestedMessage {
        string firstString = 1;
        message SecondLevelNestedMessage {
            string secondString = 2;
        }
    }
    FirstLevelNestedMessage msg = 1;
    FirstLevelNestedMessage.SecondLevelNestedMessage msg2 = 2;
}

// Any类型：允许你使用消息作为嵌入类型，且不需要它们的proto定义，Any持有任意的串行化为字节数组的数字+一个代表
// 其全局唯一类型标识符的URL
import "google/protobuf/any.proto";
message AnySampleMessage {
    repeated google.protobuf.Any.details = 1;
}

// 任意值类型的映射
map&lt;string, google.protobuf.Any&gt;
// 类似于结构
google.protobuf.Struct</pre>
<div class="blog_h2"><span class="graybg">服务 </span></div>
<p>你可以在.proto中定义一个RPC服务：</p>
<pre class="crayon-plain-tag">service UserService {
    rpc Query (string) returns (User);
}</pre>
<p>ProtoBuf编译器可以生成服务的（指定语言的）接口代码和桩代码。</p>
<div class="blog_h2"><span class="graybg">选项</span></div>
<p>你可以在.proto文件中声明一系列的选项，这些选项不会从根本上改变消息结构或者服务语义，但是会在特定上下文中影响编译器的行为：</p>
<pre class="crayon-plain-tag">// 生成的Java包名
option java_package = "cc.gmem";
// 在不同的Java文件中生成顶级消息、枚举、服务的定义
option java_multiple_files = true;
// 影响Java和C++代码生成器的行为
option optimize_for = SPEED | CODE_SIZE | LITE_RUNTIME;</pre>
<div class="blog_h2"><span class="graybg">编译</span></div>
<p>对于.proto文件：</p>
<pre class="crayon-plain-tag">syntax = "proto3";
import "google/protobuf/timestamp.proto";
package digital;

option java_package = "com.dangdang.digital";

message Media {
    uint64 mediaId = 1;
    uint64 saleId = 2;
    string title = 3;
    uint32 chapterCnt = 4;
    uint32 wordCnt = 5;
    bool isFull = 6;
    string authorName = 7;
    string publisher = 8;
    google.protobuf.Timestamp publishDate = 9;
    uint32 price = 10;
    uint32 paperBookPrice = 11;
}</pre>
<div class="blog_h3"><span class="graybg">Go</span></div>
<p>需要安装protoc-gen-go：</p>
<pre class="crayon-plain-tag">go get -u github.com/golang/protobuf/protoc-gen-go</pre>
<p>注意$GOPATH/bin需要加入到PATH环境变量。</p>
<p>执行下面的命令将上节的.proto编译为Go代码：</p>
<pre class="crayon-plain-tag">cd /home/alex/Go/workspaces/default/digital-api
mkdir digital

protoc -I=proto --go_out=./digital Media.proto</pre>
<p>生成的文件如下：</p>
<pre class="crayon-plain-tag">package digital

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/timestamp"

// 下面的语句用于抑制unused import报错
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// 生成的Go结构体，注意字段名被自动大写
type Media struct {
	MediaId        uint64                     `protobuf:"varint,1,opt,name=mediaId" json:"mediaId,omitempty"`
	SaleId         uint64                     `protobuf:"varint,2,opt,name=saleId" json:"saleId,omitempty"`
	Title          string                     `protobuf:"bytes,3,opt,name=title" json:"title,omitempty"`
	ChapterCnt     uint32                     `protobuf:"varint,4,opt,name=chapterCnt" json:"chapterCnt,omitempty"`
	WordCnt        uint32                     `protobuf:"varint,5,opt,name=wordCnt" json:"wordCnt,omitempty"`
	IsFull         bool                       `protobuf:"varint,6,opt,name=isFull" json:"isFull,omitempty"`
	AuthorName     string                     `protobuf:"bytes,7,opt,name=authorName" json:"authorName,omitempty"`
	Publisher      string                     `protobuf:"bytes,8,opt,name=publisher" json:"publisher,omitempty"`
	PublishDate    *google_protobuf.Timestamp `protobuf:"bytes,9,opt,name=publishDate" json:"publishDate,omitempty"`
	Price          uint32                     `protobuf:"varint,10,opt,name=price" json:"price,omitempty"`
	PaperBookPrice uint32                     `protobuf:"varint,11,opt,name=paperBookPrice" json:"paperBookPrice,omitempty"`
}
// 为结构定义了一些方法
func (m *Media) Reset()                    { *m = Media{} }
func (m *Media) String() string            { return proto.CompactTextString(m) }
func (*Media) ProtoMessage()               {}
func (*Media) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

// 为所有字段定义了Get
func (m *Media) GetMediaId() uint64 {
	if m != nil {
		return m.MediaId
	}
	return 0
}
// ...</pre>
<div class="blog_h3"><span class="graybg">Java</span></div>
<p>执行下面的命令将上节的.proto编译为Java代码：</p>
<pre class="crayon-plain-tag"># Java包所需的目录会自动生成
protoc -I=proto --java_out=/home/alex/JavaEE/projects/idea/digital-client/src/main/java Media.proto</pre>
<p>需要添加Maven依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.google.protobuf&lt;/groupId&gt;
    &lt;artifactId&gt;protobuf-java&lt;/artifactId&gt;
    &lt;version&gt;3.5.1&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h1"><span class="graybg">消息读写</span></div>
<div class="blog_h2"><span class="graybg">Go</span></div>
<pre class="crayon-plain-tag">media := &amp;digital.Media{
    AuthorName:     "汪震",
    ChapterCnt:     8,
    IsFull:         true,
    MediaId:        1000000,
    PaperBookPrice: 6400,
    Price:          640,
    PublishDate:    &amp;google_protobuf.Timestamp{Seconds: int64(time.Now().Second())},
}
// 序列化为字节
out, err := proto.Marshal(media)
if err != nil {
    log.Fatal(err)
}
log.Printf("marshalled size: %v", len(out))
fname := "/tmp/media.data"
ioutil.WriteFile(fname, out, 0644)

// 反序列化
in, err := ioutil.ReadFile(fname)
media = new(digital.Media)
proto.Unmarshal(in, media)
log.Println(media.AuthorName) </pre>
<div class="blog_h3"><span class="graybg">Struct类型处理</span></div>
<p>使用Struct类型，可以方便的映射map[string]any：</p>
<pre class="crayon-plain-tag">var vars map[string]any

var variables *structpb.Struct
variables, err = structpb.NewStruct(vars)
if err != nil {
	return nil, err
}
return &amp;GetInstanceVariablesResponse{
    // *_struct.Struct  对应"google/protobuf/struct.proto"的google.protobuf.Struct
	Variables: variables,
}, nil

// 要将Struct转换为map，只需要
variables.AsMap()</pre>
<div class="blog_h2"><span class="graybg">Python</span></div>
<div class="blog_h3"><span class="graybg">Struct类型处理</span></div>
<p>使用Struct类型，可以方便的映射字典：</p>
<pre class="crayon-plain-tag">from google.protobuf.struct_pb2 import Struct
from google.protobuf import json_format

data = {
    "id": 123,
    "name": "example"
}

struct_obj = Struct()
json_format.ParseDict(data, struct_obj)</pre>
<p>&nbsp;</p>
<div class="blog_h2"><span class="graybg">Java</span></div>
<pre class="crayon-plain-tag">package com.dangdang.digital;

import com.google.protobuf.Timestamp;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ProtoBufTest {

    private static final Logger LOGGER = LogManager.getLogger( ProtoBufTest.class );

    public static final String FNAME = "/tmp/media.data";

    @Test
    public void testWrite() throws IOException {

        MediaOuterClass.Media.Builder builder =
            MediaOuterClass.Media.newBuilder()
                                 .setAuthorName( "汪震" )
                                 .setChapterCnt( 8 )
                                 .setIsFull( true )
                                 .setMediaId( 1000000 )
                                 .setPaperBookPrice( 6400 )
                                 .setPrice( 640 )
                                 .setPublishDate( Timestamp.newBuilder().setSeconds( System.currentTimeMillis() / 1000 ).build() );
        // 生成Java对象
        MediaOuterClass.Media media = builder.build();
        FileOutputStream fos = new FileOutputStream( FNAME );

        // 写入到文件
        media.writeTo( fos );
    }

    @Test
    public void testRead() throws IOException {
        FileInputStream fis = new FileInputStream( FNAME );
        MediaOuterClass.Media media = MediaOuterClass.Media.parseFrom( fis );
        LOGGER.debug( media.getAuthorName() );
    }
}</pre>
<div class="blog_h1"><span class="graybg">Tag注入</span></div>
<p>开源项目<a href="https://github.com/favadi/protoc-go-inject-tag">protoc-go-inject-tag</a>支持为Protobuf生成的结构体添加自定义标签：</p>
<pre class="crayon-plain-tag">go get github.com/gmemcc/protoc-go-inject-tag</pre>
<div class="blog_h2"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">message Media {
    // @inject_tag: db:"media_id"
    uint64 mediaId = 1;
}</pre>
<p>对于上述proto，执行命令：</p>
<pre class="crayon-plain-tag">protoc -I=proto --go_out=. Media.proto
# 生成Tag：
protoc-go-inject-tag -input=Media.pb.go -remove=true</pre>
<p>最终的结构如下：</p>
<pre class="crayon-plain-tag">type Media struct {
	// @inject_tag: db:"media_id"
	MediaId uint64 `protobuf:"varint,1,opt,name=mediaId" json:"mediaId,omitempty" db:"media_id"`
} </pre>
<div class="blog_h1"><span class="graybg">gogoprotobuf</span></div>
<p><a href="gogoprotobuf">此项目</a>Fork了<a href="https://github.com/golang/protobuf">golang/protobuf</a>，提供了额外的代码生成器。支持特性：</p>
<ol>
<li>更快的序列化/反序列化</li>
<li>更加规范的Go结构</li>
<li>和goprotobuf兼容</li>
<li>支持生成测试、Benchmark代码</li>
</ol>
<div class="blog_h2"><span class="graybg">更快序列化</span></div>
<p>要生成具有更快序列化/反序列化能力的代码，安装：</p>
<pre class="crayon-plain-tag">go get github.com/gogo/protobuf/protoc-gen-gofast</pre>
<p>示例：</p>
<pre class="crayon-plain-tag">protoc --gofast_out=. myproto.proto</pre>
<p>使用此特性，就不能使用任何其它的<a href="https://github.com/gogo/protobuf/blob/master/extensions.md">gogoprotobuf扩展</a>。 </p>
<div class="blog_h2"><span class="graybg">gRPC支持</span></div>
<p>调用形式和golang/protobuf一样：</p>
<pre class="crayon-plain-tag">protoc --gofast_out=plugins=grpc:. my.proto</pre>
<div class="blog_h2"><span class="graybg">字段定制化</span></div>
<p>安装protoc-gen-gogo：</p>
<pre class="crayon-plain-tag">go get github.com/gogo/protobuf/proto
go get github.com/gogo/protobuf/jsonpb
go get github.com/gogo/protobuf/protoc-gen-gogo
go get github.com/gogo/protobuf/gogoproto</pre>
<div class="blog_h3"><span class="graybg">jsontag</span></div>
<p>此扩展允许定制生成结构的JSON Tag，示例：</p>
<pre class="crayon-plain-tag">bool include_public = 2 [(gogoproto.jsontag) = "includePublic,omitempty"];</pre>
<div class="blog_h1"><span class="graybg">protoc</span></div>
<p>此命令能够解析Proto文件，然后根据你指定的选项来生成代码。</p>
<div class="blog_h2"><span class="graybg">格式</span></div>
<pre class="crayon-plain-tag">protoc [OPTION] PROTO_FILES</pre>
<div class="blog_h2"><span class="graybg">选项</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-IPATH  -I PATH<br />--proto_path=PATH</td>
<td>
<p>指定从中搜索Imports的目录，也可以指定目标Proto文件，此选项可以指定多次。如果不指定则使用当前目录</p>
<p>如果在Proto文件中引入：</p>
<pre class="crayon-plain-tag">import "github.com/gogo/protobuf/gogoproto/gogo.proto";</pre>
<p>假设上述Proto文件在你的GOPATH下，则你需要指定 <pre class="crayon-plain-tag">-I $GOPATH/src</pre>才能生成代码</p>
</td>
</tr>
<tr>
<td>--plugin=EXECUTABLE</td>
<td>指定需要使用的插件，默认情况下protoc在$PATH下寻找插件</td>
</tr>
<tr>
<td>--cpp_out=OUT_DIR</td>
<td>生成C++头和源码</td>
</tr>
<tr>
<td>--java_out=OUT_DIR</td>
<td>生成Java源码</td>
</tr>
<tr>
<td>--js_out=OUT_DIR</td>
<td>生成JavaScript源码</td>
</tr>
<tr>
<td>--python_out=OUT_DIR</td>
<td>生成Python源码</td>
</tr>
<tr>
<td>@&lt;filename&gt;</td>
<td>从文件读取选项</td>
</tr>
<tr>
<td>--go_out</td>
<td>调用<a href="https://github.com/golang/protobuf">protoc-gen-go</a>来生成Go代码</td>
</tr>
<tr>
<td>--gogo_out</td>
<td>调用<a href="https://github.com/gogo/protobuf">protoc-gen-gogo</a>来生成Go代码</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">protoc \
    # 从这里寻找impirt
    -I$GOPATH/src  \
    # 直接指定Proto源文件
    -Ipkg/apis/chart/v1 pkg/apis/chart/v1/chart_service.proto \
    # 基于protoc-gen-gogo来生成Go代码，启用grpc支持，输出的根目录为当前目录，具体路径取决于go_package
    --gogo_out=plugins=grpc:.</pre>
<div class="blog_h1"><span class="graybg">IDE支持</span></div>
<div class="blog_h2"><span class="graybg">Intellij</span></div>
<p>插件Protobuf Support为IntelliJ平台提供了proto编辑器，完整支持Protocol Buffer 3。安装后，需要注意设置Include Path：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/02/protobuf-support-settings.png"><img class="aligncenter  wp-image-18201" src="https://blog.gmem.cc/wp-content/uploads/2018/02/protobuf-support-settings.png" alt="protobuf-support-settings" width="920" height="630" /></a></p>
<p>如果需要使用google.protobuf.Timestamp等类型，则需要将ProtoBuf安装前缀/include目录包含在列表中。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/intro-to-protocol-buffers">Protocol Buffers初探</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/intro-to-protocol-buffers/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>PhpStorm知识集锦</title>
		<link>https://blog.gmem.cc/phpstorm-faq</link>
		<comments>https://blog.gmem.cc/phpstorm-faq#comments</comments>
		<pubDate>Thu, 12 May 2016 09:58:32 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[IntelliJ]]></category>
		<category><![CDATA[XDebug]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11503</guid>
		<description><![CDATA[<p>基础知识 简介 PHPStorm是一个基于IntelliJ平台跨平台的PHP集成开发环境，支持从5.3到最新的7.0的PHP版本。该IDE同时包含了WebStorm的功能，因此如果购买了PHPStorm，不需要再购买WebStorm。 部署 PhpStorm支持将工程部署到本地或者远程服务器，或者从远程服务器同步到本地。对于远程服务器，需要进行FTP或者SFTP的配置。 部署到本地服务器 File ⇨ Settings，定位到B,E,D ⇨ Deployment，点击右侧面板的+号，出现类似下面的界面： 勾选Visible only for this project则其它工程无法看到此部署配置，Folder填写本地HTTP服务器的部署根目录。 切换到Mappings选项卡，可以配置工程中目录与服务器目录的映射关系： 在选项卡Excluded Paths中，可以排除掉不需要在工程、服务器之间同步的文件。 执行部署操作 在Project窗口中，右击某个节点，或者在编辑器上右击，均可执行Upload to Server name操作，完成部署。 <a class="read-more" href="https://blog.gmem.cc/phpstorm-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/phpstorm-faq">PhpStorm知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>PHPStorm是一个基于IntelliJ平台跨平台的PHP集成开发环境，支持从5.3到最新的7.0的PHP版本。该IDE同时包含了WebStorm的功能，因此如果购买了PHPStorm，不需要再购买WebStorm。</p>
<div class="blog_h2"><span class="graybg">部署</span></div>
<p>PhpStorm支持将工程<span style="background-color: #c0c0c0;">部署</span>到本地或者远程服务器，或者从远程服务器<span style="background-color: #c0c0c0;">同步</span>到本地。对于远程服务器，需要进行FTP或者SFTP的配置。</p>
<div class="blog_h3"><span class="graybg">部署到本地服务器</span></div>
<p>File ⇨ Settings，定位到B,E,D ⇨ Deployment，点击右侧面板的+号，出现类似下面的界面：</p>
<p><img class="aligncenter size-full wp-image-11525" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_006.png" alt="Selection_006" width="100%" /></p>
<p>勾选Visible only for this project则其它工程无法看到此部署配置，Folder填写本地HTTP服务器的部署根目录。</p>
<p>切换到Mappings选项卡，可以配置工程中目录与服务器目录的映射关系：</p>
<p><img class="aligncenter size-full wp-image-11526" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_007.png" alt="Selection_007" width="100%" /></p>
<p>在选项卡Excluded Paths中，可以排除掉不需要在工程、服务器之间同步的文件。</p>
<div class="blog_h3"><span class="graybg">执行部署操作</span></div>
<p>在Project窗口中，右击某个节点，或者在编辑器上右击，均可执行Upload to Server name操作，完成部署。</p>
<p>注意在Debug时，必须先把工程中修改的文件部署到服务器上。</p>
<div class="blog_h2"><span class="graybg">调试</span></div>
<p>File ⇨ Settings，定位到Languages &amp; Frameworks ⇨ PHP ⇨ Debug，参考下图设置：</p>
<p><img class="aligncenter size-full wp-image-11512" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_004.png" alt="Selection_004" width="100%" /></p>
<p>Run ⇨ Break at first line in PHP scripts，取消勾选，否则每个PHP文件的<span style="background-color: #c0c0c0;">第一行都会中断执行</span>，很烦人。</p>
<div class="blog_h3"><span class="graybg">PHP服务器设置</span></div>
<p>File ⇨ Settings，定位到Languages &amp; Frameworks ⇨ PHP ⇨ Servers，在此对被调试的Web服务器进行配置。</p>
<p>注意<span style="background-color: #c0c0c0;">工程目录与服务器绝对路径之间的映射关系</span>要设置正确，PHP脚本在服务器上的绝对路径会在Debug会话中传递到PhpStorm，PhpStorm依据前述的映射关系确定源码位置。你必须保证工程文件和服务器文件内容一致。</p>
<p>参考下图进行设置：</p>
<p><img class="aligncenter size-full wp-image-11516" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_0021.png" alt="Selection_002" width="100%" /></p>
<div class="blog_h3"><span class="graybg">Run/Debug配置</span></div>
<p>你还需要配置调试的入口点。点击工具栏<img class="aligncenter size-full wp-image-11521 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_0041.png" alt="Selection_004" width="46" height="17" /> 左侧的下拉按钮，选择Edit Configurations，点击+号，选择PHP Web Application，参考下图进行设置：</p>
<p><img class="aligncenter size-large wp-image-11522" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_005-1024x411.png" alt="Selection_005" width="100%" /></p>
<p>注意，如果你的服务器使用HTTPS协议，则Start URL必须填写完整的URL。</p>
<div class="blog_h3"><span class="graybg">执行调试</span></div>
<p>选中刚刚完成的Debug配置，按Alt + Shift + D或者点击工具栏上的甲虫图标即可启动调试，顺利的话，程序将在你设置好的断点处暂停。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">如何移除编辑器中的水平横线</span></div>
<p>默认情况下PHPStorm的编辑器，在类、方法前面显示一条水平横线作为分隔符，要去除，可以按如下步骤：</p>
<p>Preferences ⇨  Editor  ⇨ Colors &amp; Fonts  ⇨ General ⇨ Code ⇨ Method separator color，取消颜色设置。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/phpstorm-faq">PhpStorm知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/phpstorm-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WebStorm知识集锦</title>
		<link>https://blog.gmem.cc/webstorm-faq</link>
		<comments>https://blog.gmem.cc/webstorm-faq#comments</comments>
		<pubDate>Thu, 12 May 2016 07:06:21 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11949</guid>
		<description><![CDATA[<p>简介 WebStorm是基于IntelliJ平台的，深受前端开发者欢迎的IDE。该IDE对大量前端技术/框架，例如SASS、AngularJS 2提供及时的支持，该IDE也可以用来进行Node.js的开发。 智能提示 WebStorm能够自动索引当前模块内的JS文件，用于代码智能提示。如果JS位于其它模块中则无法提示，这种情况下可以使用外部JavaScript库机制，为任意模块提供代码智能提示。 下载库 定位到Settings ⇨ L &#38; F ⇨ JavaScript ⇨ Libraries，右边列出已经安装的JavaScript库。 点击Download，可以下载一些官方的库： 点击Download and Install即可安装相应的库。 自定义库 点击Add，可以添加自定义库，例如ExtJS 4.1.1： &#160; 点击OK即可添加新的库。 无论是下载还是手工添加的JS库，都会出现在Settings ⇨ <a class="read-more" href="https://blog.gmem.cc/webstorm-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/webstorm-faq">WebStorm知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">简介</span></div>
<p>WebStorm是基于IntelliJ平台的，深受前端开发者欢迎的IDE。该IDE对大量前端技术/框架，例如SASS、AngularJS 2提供及时的支持，该IDE也可以用来进行Node.js的开发。</p>
<div class="blog_h2"><span class="graybg"><a id="smart-code-completion"></a>智能提示</span></div>
<p>WebStorm能够自动索引<span style="background-color: #c0c0c0;">当前模块</span>内的JS文件，用于代码智能提示。如果JS位于其它模块中则无法提示，这种情况下可以使用外部JavaScript库机制，为任意模块提供代码智能提示。</p>
<div class="blog_h3"><span class="graybg">下载库</span></div>
<p>定位到Settings ⇨ L &amp; F ⇨ JavaScript ⇨ Libraries，右边列出已经安装的JavaScript库。</p>
<p>点击Download，可以下载一些官方的库：</p>
<p><img class="aligncenter size-full wp-image-11954" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Download-Library_002.png" alt="Download Library_002" width="580" height="541" /></p>
<p>点击Download and Install即可安装相应的库。</p>
<div class="blog_h3"><span class="graybg">自定义库</span></div>
<p>点击Add，可以添加自定义库，例如ExtJS 4.1.1：<img class="aligncenter size-full wp-image-11956" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Edit-Library_003.png" alt="Edit Library_003" width="512" height="476" /></p>
<p>&nbsp;</p>
<p>点击OK即可添加新的库。</p>
<p>无论是下载还是手工添加的JS库，都会出现在Settings ⇨ L &amp; F ⇨ JavaScript ⇨ Libraries右侧的库列表中，勾选的库对当前工程可见。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/webstorm-faq">WebStorm知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/webstorm-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RubyMine知识集锦</title>
		<link>https://blog.gmem.cc/rubymine-faq</link>
		<comments>https://blog.gmem.cc/rubymine-faq#comments</comments>
		<pubDate>Wed, 02 Mar 2016 01:22:07 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11733</guid>
		<description><![CDATA[<p>简介 RubyMine是一款基于IntelliJ平台的IDE，用于Ruby以及Ruby on Rails开发。该IDE内置了对JavaScript、HTML等Web开发技术的支持。 全局设置 设置路径 说明 L &#38; F ⇨ Ruby SDK &#38; Gems 可以管理Ruby SDK，点击按钮可以添加SDK，点击按钮可以设置当前工程使用的SDK 点击右侧窗格中的则可以为某个SDK安装Gem 常见问题 无法安装调试需要的Gem Ubuntu 14.04下，调试时RubyMine提示：The gem <a class="read-more" href="https://blog.gmem.cc/rubymine-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/rubymine-faq">RubyMine知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">简介</span></div>
<p>RubyMine是一款基于IntelliJ平台的IDE，用于Ruby以及Ruby on Rails开发。该IDE内置了对JavaScript、HTML等Web开发技术的支持。</p>
<div class="blog_h2"><span class="graybg">全局设置</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">设置路径</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>L &amp; F ⇨ Ruby SDK &amp; Gems</td>
<td>
<p>可以管理Ruby SDK，点击按钮<img class="aligncenter size-full wp-image-11736 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/03/Selection_003.png" alt="Selection_003" width="15" height="16" />可以添加SDK，点击按钮<img class="aligncenter size-full wp-image-11737 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/03/Selection_004.png" alt="Selection_004" width="15" height="13" />可以设置当前工程使用的SDK</p>
<p>点击右侧窗格中的<img class="aligncenter size-full wp-image-11736 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/03/Selection_003.png" alt="Selection_003" width="15" height="16" />则可以为某个SDK安装Gem</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">无法安装调试需要的Gem</span></div>
<p>Ubuntu 14.04下，调试时RubyMine提示：The gem debase required by the debugger is not currently installed. would you like to install it ?</p>
<p>点击Yes后，安装失败，提示： ERROR: Failed to build gem native extension ... Installing base gem Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources</p>
<p>解决方法一，使用非HTTPS源：</p>
<pre class="crayon-plain-tag">gem source -r https://rubygems.org/
gem source -a http://rubygems.org/</pre>
<p>解决方法二，安装libssl-dev并重新构建Ruby：</p>
<pre class="crayon-plain-tag">sudo apt-get install libssl-dev
cd ruby-2.1.9
./configure prefix=/home/alex/Ruby/2.1.9
make clean
make &amp;&amp; make install</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/rubymine-faq">RubyMine知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/rubymine-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IntelliJ IDEA知识集锦</title>
		<link>https://blog.gmem.cc/intellij-idea-faq</link>
		<comments>https://blog.gmem.cc/intellij-idea-faq#comments</comments>
		<pubDate>Fri, 29 Jan 2016 01:38:47 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11198</guid>
		<description><![CDATA[<p>简介 IDEA是一个历史悠久的Java集成开发环境，它始于2001年，以智能化著称。IDEA基于IntelliJ平台，分为社区版、商业版。IDEA目前自带JRE，此JRE仅仅用于IDEA的运行，不能做开发用。 核心概念 工程（Project） 在IDEA中，你总是在“工程”的上下文中工作，工程代表了一个完整的软件解决方案，包括代码助手、重构、代码风格一致性在内的功能都在工程内进行。 工程本身不包含开发相关的内容，例如源码、构建脚本或者文档。它主要用于组织模块并提供公共的设置。模块才是代码的容器。 工程格式和元数据文件 IDEA使用普通的XML文件来存储工程的配置信息、及其包含的组件。并支持两种存储工程配置的格式： 基于目录的格式 在工程目录下的[crayon-69dc3b0cad6cc537670226-i/] 子目录中存放配置，该子目录包含一系列XML文件，包括：compiler.xml、encodings.xml、modules.xml等 这些文件记录工程本身的核心信息，包括：模块组件的名称和位置、编译器设置等，可以存放到VCS。一个例外是workspace.xml，该文件存储个人设置（例如窗口位置）以及其它附属于开发环境的信息，不应该存放到VCS 基于文件的格式 在工程目录中使用两类文件： *.ipr存储工程核心信息 *.iws存储个人设置 IDEA允许在这两种格式之间进行转换：File ⇨ Save as Directory-Based Format 模块（Module） 模块是工程中一个可以独立编译、运行、调试、测试的单元。模块的配置信息默认存放在其内容根目录（Content <a class="read-more" href="https://blog.gmem.cc/intellij-idea-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/intellij-idea-faq">IntelliJ IDEA知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>IDEA是一个历史悠久的Java集成开发环境，它始于2001年，以智能化著称。IDEA基于IntelliJ平台，分为社区版、商业版。IDEA目前自带JRE，此JRE仅仅用于IDEA的运行，不能做开发用。</p>
<div class="blog_h1"><span class="graybg">核心概念</span></div>
<div class="blog_h2"><span class="graybg">工程（Project）</span></div>
<p>在IDEA中，你总是在“工程”的上下文中工作，<span style="background-color: #c0c0c0;">工程代表了一个完整的软件解决方案</span>，包括代码助手、重构、代码风格一致性在内的功能都在工程内进行。</p>
<p>工程本身不包含开发相关的内容，例如源码、构建脚本或者文档。它主要用于组织模块并提供公共的设置。模块才是代码的容器。</p>
<div class="blog_h3"><span class="graybg">工程格式和元数据文件</span></div>
<p>IDEA使用普通的XML文件来存储工程的配置信息、及其包含的组件。并支持<span style="background-color: #c0c0c0;">两种存储工程配置的格式</span>：</p>
<table class=" fixed-word-wrap full-width">
<tbody>
<tr>
<td style="width: 20%; text-align: center;"><strong>基于目录的格式</strong></td>
<td>
<p>在工程目录下的<pre class="crayon-plain-tag">.idea</pre> 子目录中存放配置，该子目录包含一系列XML文件，包括：compiler.xml、encodings.xml、modules.xml等</p>
<p>这些文件记录工程本身的核心信息，包括：模块组件的名称和位置、编译器设置等，可以存放到VCS。一个例外是workspace.xml，该文件存储个人设置（例如窗口位置）以及其它附属于开发环境的信息，不应该存放到VCS</p>
</td>
</tr>
<tr>
<td style="text-align: center;"><strong>基于文件的格式</strong></td>
<td>
<p>在工程目录中使用两类文件：</p>
<ol>
<li>*.ipr存储工程核心信息</li>
<li>*.iws存储个人设置</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p>IDEA允许在这两种格式之间进行转换：File ⇨ Save as Directory-Based Format</p>
<div class="blog_h2"><span class="graybg">模块（Module）</span></div>
<p>模块是<span style="background-color: #c0c0c0;">工程中</span>一个可以<span style="background-color: #c0c0c0;">独立</span>编译、运行、调试、测试的单元。模块的配置信息默认存放在其内容根目录（Content root folder）下的<pre class="crayon-plain-tag">.iml</pre> 文件中，该文件一般存放到VCS。</p>
<div class="blog_h3"><span class="graybg">Content Root</span></div>
<p>Content root指的是包含模块所有文件目录。IDEA允许一个模块拥有多个Content root，但是通常都是一个。</p>
<p>某些时候没有Conent root的模块会有特殊用途，例如用来指定一组依赖关系。</p>
<div class="blog_h3"><span class="graybg">目录类型与图标</span></div>
<p>IDEA使用不同图标来区分模块中不同的目录。你可以在目录上右键 ⇨ Mark Directory As来设置目录的类型。常用的目录类型及图标包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 50px; text-align: center;">图标</td>
<td style="text-align: center;">目录类型说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><img class="aligncenter size-full wp-image-11839 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/moduleFolder.png" alt="moduleFolder" width="15" height="13" /></td>
<td>Content Root：标识一个模块的内容根目录</td>
</tr>
<tr>
<td style="text-align: center;"><img class="aligncenter size-full wp-image-11840 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/folder.png" alt="folder" width="14" height="12" /></td>
<td>普通目录</td>
</tr>
<tr>
<td><img class="aligncenter size-full wp-image-11847" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootSource.gif" alt="rootSource" width="14" height="12" /></td>
<td>源代码根目录，如果把某个目录分配为此类型，则IDEA会在构建过程中编译其中的源代码</td>
</tr>
<tr>
<td><img class="aligncenter size-full wp-image-11846" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootGeneratedSourceIJ.png" alt="rootGeneratedSourceIJ" width="15" height="13" /></td>
<td>自动生成的源代码根目录</td>
</tr>
<tr>
<td><img class="aligncenter size-large wp-image-11845" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootTest.gif" alt="rootTest" width="14" height="12" /></td>
<td>测试源代码根目录，用于将测试代码和产品代码隔离</td>
</tr>
<tr>
<td><img class="aligncenter size-large wp-image-11844" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootGeneratedTestSourceIJ.png" alt="rootGeneratedTestSourceIJ" width="15" height="13" /></td>
<td>自动生成的测试源代码根目录</td>
</tr>
<tr>
<td><img class="aligncenter size-large wp-image-11843" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootResourceIJ.png" alt="rootResourceIJ" width="15" height="13" /></td>
<td>资源文件根目录，在构建过程中会原样拷贝到输出目录</td>
</tr>
<tr>
<td><img class="aligncenter size-large wp-image-11842" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootTestResourceIJ.png" alt="rootTestResourceIJ" width="15" height="13" /></td>
<td>测试资源文件根目录</td>
</tr>
<tr>
<td><img class="aligncenter size-full wp-image-11841" src="https://blog.gmem.cc/wp-content/uploads/2016/01/rootExcluded.gif" alt="rootExcluded" width="14" height="12" /></td>
<td>
<p>排除目录，这类目录“几乎”被IDEA忽略<br />该目录中的代码得到受限制的代码提示，外部目录中的代码的<span style="background-color: #c0c0c0;">自动完成列表不会出现该目录中的内容</span>，<span style="background-color: #c0c0c0;">搜索时IDEA忽略</span>此目录</p>
<p>将某些不重要的目录设置为排除目录，可以提高IDE的性能</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">依赖</span></div>
<p>一个模块的依赖是指，该模块使用的（因而依赖于的）其它<span style="background-color: #99cc00;">实体</span>。一个模块依赖可以是：模块使用的<span style="background-color: #99cc00;">SDK</span>、模块使用的<span style="background-color: #99cc00;">库</span>、相同工程中的<span style="background-color: #99cc00;">其它模块</span>。术语“模块依赖”专指第三种。</p>
<p>依赖的优先级由Project Structure ⇨ Modules ⇨ Dependencies页签指定，你可以手工调整顺序：</p>
<p><img class="aligncenter size-full wp-image-11849" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_005.png" alt="Selection_005" width="100%" /></p>
<p>顺序决定了在编译期间，javac寻找类的优先级；也决定了运行时（普通Java程序）寻找类的优先级。 </p>
<div class="blog_h2"><span class="graybg">Facet</span></div>
<p>Facet代表模块使用的框架、技术或者语言。一个模块可以添加多个Facet，使用某些Facet可能需要下载框架组件。</p>
<div class="blog_h2"><span class="graybg">构件（Artifact）</span></div>
<p>在IDEA中构件这一术语，可以指定两个不同却相关的事物：</p>
<ol>
<li>构件可以指你期望从工程输出的内容的规则（specification）</li>
<li>构件可以指上述规则的实际产出</li>
</ol>
<p>构件可以简单的是一个或者多个模块打包而成的JAR、WAR或者EAR，对于后两者，Web/Java EE Application的Facet相关的资源会一并打包到WAR/EAR中。你也可以生成自定义格式的构件。</p>
<p>简而言之，构件允许你把（一个或者多个模块）编译后的代码、关联的库、资源文件、元数据文件（例如Facet的部署描述符）等一并成一个可部署的单元。</p>
<p>构件还可以包含其它构件。</p>
<div class="blog_h2"><span class="graybg">库</span></div>
<p>库是指在你的模块中以只读方式使用的、已编译的代码的集合。</p>
<p>库可以附带源码、API文档，这可以方便的进行Debug，或者在IDE中查看inline文档。</p>
<div class="blog_h3"><span class="graybg">库的级别</span></div>
<p>你可以定义三种级别的库：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">库级别</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>全局（IDE）</td>
<td>这类库可以被任何工程使用，即它们可以被加入到任何工程的任何模块的<span style="background-color: #c0c0c0;">依赖列表</span>中</td>
</tr>
<tr>
<td>工程</td>
<td>这类库可以被工程内部所有模块使用</td>
</tr>
<tr>
<td>模块</td>
<td>这类库只能被单个模块使用</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">应用服务器库</span></div>
<p>这类库允许你使用特定于某种JavaEE服务器/Servlet容器的类。每当你添加了一个应用程序定义后，IDEA会自动创建相应的应用服务器库：</p>
<p><img class="aligncenter size-full wp-image-11851" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Choose-Libraries_001.png" alt="Choose Libraries_001" width="524" height="178" /></p>
<div class="blog_h2"><span class="graybg">路径变量</span></div>
<p>为了减少在开发机器之间转移工程的复杂性，IDEA引入路径变量（path variables），路径变量指向一个目录/文件的绝对路径。当使用<span style="background-color: #c0c0c0;">位于工程外部的第三方库</span>时，路径变量特别有用。</p>
<div class="blog_h2"><span class="graybg">Scope</span></div>
<p>在IDEA中Scope表示一组文件、包或者目录。使用Scope你可以得到一个过滤后的视图，用于关注于特定操作。对于大规模的工程来说，Scope特别有效。IDEA提供了一系列预定义的Scope，你还可以自定义Scope。</p>
<p>你可以在Project工具窗口中浏览Scope。</p>
<div class="blog_h3"><span class="graybg">Scope的类型</span></div>
<p>可以分为两类：</p>
<ol>
<li>Shared Scopes：可以供团队成员使用，存放在.idea/scopes目录或*.ipr中（这也是为什么工程元数据可能需要提交到VCS的原因）</li>
<li>Local Scopes：自己本地使用</li>
</ol>
<div class="blog_h3"><span class="graybg">预定义Scope</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">Scope</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Project Files</td>
<td>包含工程内容根目录下所有的文件，模块依赖一般不包含在其中</td>
</tr>
<tr>
<td>Problems</td>
<td>工程内容根目录下存在错误的文件</td>
</tr>
<tr>
<td>Project</td>
<td>包含Project Files以及所有模块依赖</td>
</tr>
<tr>
<td>Production</td>
<td>类似于Project，但是排除测试相关的目录文件</td>
</tr>
<tr>
<td>Tests</td>
<td>Project中出去Production的部分</td>
</tr>
<tr>
<td>Changed Files</td>
<td>修改后的文件</td>
</tr>
<tr>
<td>Open files</td>
<td>在编辑器中打开的文件</td>
</tr>
<tr>
<td>Current file</td>
<td>编辑器中当前显示的文件</td>
</tr>
<tr>
<td>Selected files</td>
<td>当前被选中的文件（例如在Project工具窗口中）</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">编译和构建</span></div>
<p>所谓编译，就是指把*.java源文件通过javac/eclipse/ajc等编译器处理，生成*.class文件。</p>
<p>所谓构建，是指编译源代码、拷贝资源文件，必要的情况下进行打包。</p>
<p>编译、构建的<span style="background-color: #c0c0c0;">结果，总是存放在工程/模块的输出目录下</span>，默认的输出目录为out，<span style="background-color: #c0c0c0;">使用Maven时一般为target</span>。</p>
<p>在IDEA的Build菜单下，有多个相似的菜单项，说明如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">菜单项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Make Project</td>
<td rowspan="2">只编译修改过的文件，使用的最多，对于大型工程比Rebuild节约时间</td>
</tr>
<tr>
<td>Make Module ...</td>
</tr>
<tr>
<td>Compile ...</td>
<td>强制编译单个文件，不管上次编译依赖是否修改过</td>
</tr>
<tr>
<td>Rebuild Project</td>
<td>强制重新构建整个工程，默认的会在构建之前清空输出目录</td>
</tr>
<tr>
<td>Build Artifact</td>
<td>创建构件，对于exploded类型的构件，仅仅是将散装的构件目录存放到输出目录中<br />在Tomcat中运行构件时，直接把上述构件目录作为Web应用的根目录</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">全局设置</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">设置路径</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>B,E,D  ⇨ Compiler</td>
<td>勾选Make project automatically，修改代码后IDEA会自动编译Java类</td>
</tr>
<tr>
<td>B,E,D ⇨ Build Tools ⇨ Maven</td>
<td>取消勾选Always update snapshots，勾选会导致总去远程仓库下载<br />Maven home directory设置为/home/alex/JavaEE/maven/3.0.5<br />User settings file 设置为/home/alex/JavaEE/config/maven-settings.xml，勾选Override</td>
</tr>
<tr>
<td>B,E,D ⇨ Build Tools ⇨ Maven ⇨ Importing</td>
<td>勾选Import Maven projects automatically，保持Maven工程模型和IDEA工程模型之间的同步<br />勾选Automatically download Sources</td>
</tr>
<tr>
<td>B,E,D ⇨ Build Tools ⇨ Maven ⇨ Repositories</td>
<td>选中Remote Repository，执行Update操作</td>
</tr>
<tr>
<td>B,E,D ⇨ Application Servers</td>
<td>配置应用服务器</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用快捷键</span></div>
<p>下表列出的快捷键，全部基于Eclipse的Keymaps。</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">快捷键</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Ctrl + Shift + Alt + S</td>
<td>打开工程结构对话框，可以进行工程、模块、依赖库等方面的设置</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"><a id="webapp-dev"></a>Web应用开发</span></div>
<div class="blog_h2"><span class="graybg">创建Web工程</span></div>
<ol>
<li>File ⇨ New ⇨ Project，弹出New Project对话框</li>
<li>切换到Java Enterprise选项卡，选择合适的Java EE version。默认情况下是Java EE 7</li>
<li>切换到Java选项卡，选择合适的Project SDK。注意，<span style="background-color: #c0c0c0;">这里可以添加JDK</span>，以后的工程自动感知已添加的JDK</li>
<li>勾选Java EE ⇨ Web Application，Servlet规范的版本会显示在下面</li>
<li>下一步，给工程命名并指定目录位置，完成</li>
</ol>
<div class="blog_h3"><span class="graybg">添加JavaEE库</span></div>
<p>工程src目录右击 ⇨ New ⇨ Servlet，创建任意一个Servlet，IDEA会触发错误提示，原因是没有找到Servlet API。</p>
<p>在代码中红色HttpServlet文字上Alt + Enter，Add Java EE 6 JARs to module dependencies。在弹出对话框中点选Download，然后点击Configure，参考喜下图设置：</p>
<p><img class="aligncenter size-full wp-image-11809" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_012.png" alt="Selection_012" width="100%" /></p>
<p>确定后，IDEA会下载JavaEE库。这些库会在Project Structure的Platform Settings ⇨ Global Libraries中出现，并且被当前工程自动引用。Servlet中的报错会消失，JSP也可以获得代码自动完成提示了。</p>
<div class="blog_h2"><span class="graybg">部署到服务器</span></div>
<div class="blog_h3"><span class="graybg">配置构件</span></div>
<p>File ⇨ Project Structure，切换到Artifacts，配置“构件”（如果使用Maven或者Gradle等构建工具，则不需要在此配置）。参考下图：</p>
<p><img class="aligncenter size-full wp-image-11794" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_008.png" alt="Selection_008" width="100%" /></p>
<p>默认的，IDEA已经生成一个构件——解包（exploded）部署的war。点击<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" />可以定义新的构件，构件类型包括JAR、WAR、EAR、JavaFx应用等等。</p>
<p>在右侧窗格，你可以修改构件的名称、输出目录。勾选Build on make，则在<span style="background-color: #c0c0c0;">构建工程时，IDEA会自动生成此构件</span>。</p>
<p>在下面的Output Layout选项卡中，可以定义构件包含的内容、目录结构：</p>
<ol>
<li>在此选项卡的左侧，可以为构件添加目录、归档文件，或者添加工程组件的一个拷贝——例如模块的编译输出（Module Output）、目录和文件、JavaEE资源等</li>
<li>在此选项卡的右侧，可以双击，把某个元素添加到构件默认位置，或者拖拽到构件的任意位置。已经添加过的元素，不显示</li>
</ol>
<div class="blog_h3"><span class="graybg">配置服务器</span></div>
<p>File ⇨ Settings ⇨ B,E,D ⇨ Application Servers，点击<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" />可以配置新的服务器。IDEA支持绝大部分主流的JavaEE应用/Web服务器。</p>
<div class="blog_h3"><span class="graybg">部署构件</span></div>
<p>Run ⇨ Edit Configurations，点击<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" />创建新的运行/调试配置。以Tomcat为例，选择Tomcat Sever ⇨ Local。在右侧窗格顶部可以<span style="background-color: #c0c0c0;">设置此配置的名称</span>，例如“Tomcat v8.0 Server at localhost:8080”。右侧窗格有多个选项卡，可以分别进行配置：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 18%; text-align: center;">选项卡</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Server</td>
<td>
<p>选择先前配置过的Application Server，指定JVM参数，Tomcat端</p>
<p>On 'Update' action，指定Update操作（Ctrl + F10）的行为，对应Run/Debug工具窗口中的<img class="aligncenter size-full wp-image-11798 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_009.png" alt="Selection_009" width="17" height="16" />按钮</p>
<p>On frame deactivation可以指定当IDEA失去焦点（切换到其它程序）后执行的操作，对应Run/Debug工具窗口⇨Deployment选项卡中的<img class="aligncenter size-full wp-image-11799 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_010.png" alt="Selection_010" width="14" height="15" />按钮</p>
<p>配置参考图如下：<img class="aligncenter size-full wp-image-11800" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_011.png" alt="Selection_011" width="100%" /></p>
</td>
</tr>
<tr>
<td>Deployment</td>
<td>指定服务器启动时需要部署的构件，对于War构件，可以为其指定一个Application Context，<span style="background-color: #c0c0c0;">对应了URL </span></td>
</tr>
<tr>
<td>Logs</td>
<td>指定在Run/Debug工具窗口中显示哪些服务器日志（每个日志占据一个选项卡 ）</td>
</tr>
<tr>
<td>Code Coverage</td>
<td>进行代码覆盖率检查的配置 </td>
</tr>
<tr>
<td>Startup/Connection</td>
<td>指定Debug、Run或者Coverage时执行的命令行、JVM参数、环境变量</td>
</tr>
</tbody>
</table>
<p>在选项卡的下面，还有一个Before launch配置，可以指定启动服务器之前需要执行的动作。 </p>
<div class="blog_h2"><span class="graybg">运行Web应用</span></div>
<p>在工具栏上选择运行配置<img class="aligncenter size-full wp-image-11812 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_013.png" alt="Selection_013" width="276" height="20" />，然后点击右侧的<img class="aligncenter size-full wp-image-11521 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/05/Selection_0041.png" alt="Selection_004" width="46" height="17" />按钮，IDEA将启动应用服务器（运行/调试模式）并且部署Web应用。</p>
<p>在运行期间，依据运行配置的On Update action、On frame deactivation设置，IDEA可能自动把更新后的类、资源文件同步到服务器。你可以在Application Servers工具窗口中查看服务器代码是不是最新的：</p>
<p><img class="aligncenter size-full wp-image-11813" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_015.png" alt="Selection_015" width="390" height="75" /></p>
<p>如果某个构件后面跟着[Republish]，你可以点击<img class="aligncenter size-full wp-image-11814 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_016.png" alt="Selection_016" width="14" height="17" />按钮，将最新的代码同步到服务器。</p>
<p>当你从结构上更改一个类，比如修改了一个方法的签名，或者修改某个框架的配置文件，IDEA无法进行热部署，必须重启服务器。此时你可以借助JRebel插件。</p>
<div class="blog_h2"><span class="graybg">智能提示</span></div>
<p>参考：<a href="/webstorm-faq#smart-code-completion">WebStorm智能提示</a></p>
<div class="blog_h3"><span class="graybg">下载库</span></div>
<p>下载一个官方库后，IDEA会弹出一个对话框，你有机会将库添加为某个模块的依赖：</p>
<p><img class="aligncenter size-full wp-image-11960" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Choose-Modules-for-ExtJS-GPL-4.2_001.png" alt="Choose Modules for ExtJS GPL-4.2_001" width="510" height="543" /></p>
<p>被添加JS库依赖的模块，可以在Project Structure ⇨ Modules对话框点选模块，然后在右侧Dependencies中看到此依赖：</p>
<p><img class="aligncenter size-full wp-image-11961" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Project-Structure_0021.png" alt="Project Structure_002" width="100%" /></p>
<p>遗憾的是，通过<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" />按钮你只能添加Java库，看不到JS库，这可能是个BUG。</p>
<div class="blog_h3"><span class="graybg">自定义库</span></div>
<p>自定义库在IDEA 2016.1下无法工作（你没有机会将其添加为模块依赖），可能是BUG 。</p>
<p>手工解决办法是，先添加好自定义库，然后修改相关模块的.iml文件，添加：</p>
<pre class="crayon-plain-tag">&lt;!-- 添加类似下面的一行 --&gt;    
    &lt;!-- name为你的自定义库的名称 --&gt;
    &lt;!-- 如果你的库的Type是Global则level设置为application，否则设置project --&gt;
    &lt;orderEntry type="library" name="ExtJS 4.1.1" level="application" /&gt;
  &lt;/component&gt;
&lt;/module&gt;</pre>
<p>修改完毕后立即生效。</p>
<div class="blog_h1"><span class="graybg">从Eclipse迁移</span></div>
<div class="blog_h3"><span class="graybg">IDEA没有工作区</span></div>
<p>IDEA没有工作区（Workspace）这一概念，这意味着你同时只能（在一个窗口）处理一个工程（Project）。在Eclipse中，你常常把<span style="background-color: #99cc00;">若干个相互关联的工程</span>在同一工作区中打开，在IDEA中你必须将<span style="background-color: #99cc00;">它们</span>组织为<span style="background-color: #c0c0c0;">单个工程的多个模块</span>。对于<span style="background-color: #c0c0c0;">不相关的工程，建议在多个IDEA窗口</span>中打开。</p>
<p>Eclipse和IDEA中重要术语的对应关系如下表：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">Eclipse</td>
<td style="text-align: center;">IntellJ IDEA</td>
</tr>
</thead>
<tbody>
<tr>
<td>Workspace</td>
<td>Project</td>
</tr>
<tr>
<td>Project</td>
<td>Module </td>
</tr>
<tr>
<td>Facet</td>
<td>Facet</td>
</tr>
<tr>
<td>Library</td>
<td>Library</td>
</tr>
<tr>
<td>JRE </td>
<td>SDK </td>
</tr>
<tr>
<td>Classpath variable</td>
<td>Path variable</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">IDEA没有Perspective</span></div>
<p>IDEA同样没有透视图（Perspective）的概念，这个特点和Visual Studio类似，但和Eclipse迥异。在Eclipse中你可以切换到不同的透视图，来完成相应的工作，IDEA则没有这个必要，IDEA会智能的管理工具窗口——类似于Eclipse中的视图（View），这些<span style="background-color: #c0c0c0;">窗口会在和当前任务相关时显示</span>。</p>
<p>你可以拖拽以重新排列工具窗口，或者设置其为浮动模式。通过Window ⇨ Store Current Layout as Default可以保存当前工具窗口的布局，对以后所有工程生效。</p>
<p>某些工具窗口可以和编辑器联动，在工具窗口标题上栏上点击右键：</p>
<ol>
<li>Autoscroll to Source：点击工具窗口中的条目后，编辑器自动切换到对应文件或者代码行</li>
<li>Autoscroll from Source：切换编辑器或代码行后，自动选中工具窗口中匹配的条目</li>
</ol>
<p>这两项功能默认都是关闭的。Scroll from Source可以通过<img class="aligncenter size-full wp-image-11876 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/locate.png" alt="locate" width="14" height="16" />按钮手工触发。</p>
<div class="blog_h3"><span class="graybg">无需“保存”</span></div>
<p>IDEA会自动保存修改后的文件，并且能够从Local History中撤销重构。你之前不停按Ctrl + S的习惯可以改改了。IDEA会在编译、关闭文件时、IDE失去焦点时自动存储文件到磁盘。你还可以在A &amp; B ⇨ System Settings 中设置IDE空闲多长时间后自动保存。</p>
<div class="blog_h3"><span class="graybg">编译方式</span></div>
<p>默认情况下IDEA不会自动的编译代码。你可以开启自动编译，但是当有程序在运行时，自动编译不会执行，此时你必须Ctrl + F9强制编译。</p>
<p>Eclipse使用自己的编译器，而IDEA默认使用JDK中的javac。Eclispse编译器对错误更加容忍，有些时候允许运行不能通过编译的代码。要切换到Eclipse编译器，可以B,E,D ⇨ Compiler ⇨ Java Compiler，设置Use compiler。要允许执行无法编译的代码，需要在Run/Debug Configurations中把Make替换为Make, no error check</p>
<div class="blog_h3"><span class="graybg">代码助手</span></div>
<p>要执行类似于Eclipse的Quick fix（Ctrl + 1），可以Alt + Enter。</p>
<p>要自动生成构造器、Getter/Setter，可以Alt + Insert。</p>
<p>IDEA的自动完成功能更加智能化，也比较复杂，分为：基本自动完成、次要基本自动完成、智能完成、次要智能完成、语句补全。</p>
<p>按Ctrl + J 可以插入模板代码，按Ctrl + Alt + J可以用模板围绕选中代码。你可以在Editor ⇨ Live Templates添加自己的模板。</p>
<p>IDEA支持特别的Postfix自动完成，你可以在Editor ⇨ General ⇨ Postfix Completion中查看可用的Postfix模板。</p>
<div class="blog_h3"><span class="graybg">运行与调试</span></div>
<p>前面提到过，即使你设置了自动编译，在程序运行时IDEA不会自动编译。这样代码就不会Auto reload，要自动重新载入改变的类，你需要：</p>
<ol>
<li>对于普通Java程序：明确构建Ctrl + F9</li>
<li>对于服务器上的程序：通过Ctrl + F10更新服务器</li>
</ol>
<div class="blog_h1"><span class="graybg">与构建工具集成</span></div>
<p>目前Java领域流行的构建工具包括Maven、Gradle，IDEA对它们提供了良好的内置支持。</p>
<p>当你创建/导入一个Maven/Gradle工程后，可以自由的修改pom.xml、build.gradle。<span style="background-color: #c0c0c0;">修改这些配置文件</span>引发潜在的<span style="background-color: #c0c0c0;">构建配置发生的改变</span>必须<span style="background-color: #c0c0c0;">最终同步到IDEA的工程模型</span>中。当你在配置文件中声明WAR等构件时，IDEA会自动配置Project Structure ⇨ Artifacts下的IDEA构件。</p>
<p>要<span style="background-color: #c0c0c0;">手工完成上述同步</span>，可以在Maven/Gradle工具窗口中点击<img class="aligncenter size-full wp-image-11819 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_001.png" alt="Selection_001" width="15" height="16" />按钮。要启用自动化同步，可以：</p>
<ol>
<li>对于Maven：B,E,D ⇨ Build Tools ⇨ Maven ⇨ Importing，勾选Import Maven projects automatically</li>
<li>对于Gradle：B,E,D ⇨ Build Tools ⇨ Gradle，勾选Use auto-import</li>
</ol>
<p>注意：</p>
<ol>
<li>IDEA允许任何Maven Goal或者Gradle Task在“运行”之前执行（通过Run/Debug Configuration设置）</li>
<li>当在IDEA中执行编译、构建操作时，IDEA使用自己的构建流程。此流程一般会比较快，但是可能和Maven/Gradle构建的结果不一致，当不一致发生时，你可以使用Maven goal或者Gradle task代替IDEA的Make</li>
</ol>
<div class="blog_h2"><span class="graybg">Maven</span></div>
<div class="blog_h3"><span class="graybg">自动触发执行</span></div>
<p>在Maven Projects工具窗口，你可以右击<span style="background-color: #c0c0c0;">生命周期阶段或者目标</span>，并指定在<span style="background-color: #c0c0c0;">Make/Rebuild/Run之前/后</span>自动触发执行。</p>
<div class="blog_h3"><span class="graybg">target目录</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">子目录/文件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>classes</td>
<td>IDEA的Make、Rebuild操作生成的产品Java类、资源文件目录<br />Maven相关goal生成的产品Java类、资源文件目录</td>
</tr>
<tr>
<td>test-classes</td>
<td>IDEA的Make、Rebuild操作生成的测试Java类、资源文件目录<br />Maven相关goal生成的测试Java类、资源文件目录</td>
</tr>
<tr>
<td>artifactid-version</td>
<td>
<p>IDEA的Build Artifacts操作生成的artifactid:war explodeed构件的存放目录<br />Maven相关goal生成的解包格式的war的存放目录，已经正确处理好overlay+excluded</p>
<p><strong><span style="color: #ffffff; background-color: #ff0000;">注意：</span></strong></p>
<ol>
<li>在Tomcat上执行构件时，Tomcat的Context的docBase就是该目录，即不需要像Eclipse那样从target拷贝文件到外部的tmpN目录</li>
<li>对Tomcat执行Update操作后，最新的已加载类、最新的资源文件自动同步到该目录</li>
<li>如果该目录由Maven生成，后续对构件进行Build操作，不会破坏maven-war-plugin的excluded规则</li>
<li>如果该目录由Maven生成，后续对构件进行Rebuild操作，则maven-war-plugin的excluded规则被破坏</li>
</ol>
</td>
</tr>
<tr>
<td>artifactid-version.war</td>
<td>IDEA的Build Artifacts操作生成的artifactid:war构件<br />Maven相关goal生成的war，已经正确处理好overlay+excluded</td>
</tr>
<tr>
<td>war/work</td>
<td>maven-war-plugin工作目录，存放参与overlay且尚未exclude的war包的所有文件</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">实例：既有复杂工程的迁移</span></div>
<div class="blog_h2"><span class="graybg">工程简介</span></div>
<p>这是一个真实的、已经维护了多年的工程，由5个Maven工程组成，其中一个包含11个Maven模块。这些工程使用了AspectJ编译时织入、maven-war-plugin的overlay、Spring、Hibernate等框架或插件。</p>
<div class="blog_h2"><span class="graybg">创建IDEA容器工程</span></div>
<p>Create New Project ⇨ Empty Project，创建一个空白的容器工程：</p>
<p><img class="aligncenter size-full wp-image-11864" src="https://blog.gmem.cc/wp-content/uploads/2016/01/New-Project_001.png" alt="New Project_001" width="100%" /></p>
<p>这样的工程创建后，工程内容根目录中除了.idea目录外没有任何内容。打开此工程后，IDEA自动打开Project Structure对话框，提示添加模块。</p>
<div class="blog_h2"><span class="graybg">选择待导入模块</span></div>
<p>为了<span style="background-color: #c0c0c0;">便于管理</span>，我们把工程牵涉到的模块（一系列Maven工程）从VCS签出，<span style="background-color: #c0c0c0;">存放在新工程pems-trunk的根目录</span>下。然后点击<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" /> ⇨  Import Module，选择从工程根目录导入，如下图：</p>
<p><img class="aligncenter size-full wp-image-11879" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Select-File-or-Directory-to-Import_001.png" alt="Select File or Directory to Import_001" width="693" height="492" /></p>
<p>点击OK，出现如下对话框：</p>
<p><img class="aligncenter size-full wp-image-11880" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Import-Module_001.png" alt="Import Module_001" width="100%" /></p>
<p>选择Maven，点击Next。注：通过Maven Projects工具窗口中的<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" />进行导入，与此导入方式效果一样。</p>
<div class="blog_h2"><span class="graybg">Maven工程导入</span></div>
<p>点击Next后，出现如下对话框：</p>
<p><img class="aligncenter size-full wp-image-11886" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Import-Module_0011.png" alt="Import Module_001" width="100%" /></p>
<p>设置如下：</p>
<ol>
<li>勾选Search for projects recurively，以便递归的在子目录中查找Maven工程</li>
<li>勾选Keep project file in，指定一个位置（例如.idea目录，或者工程根目录），可以集中的存放*.iml文件，避免对纯Maven工程的污染</li>
<li>勾选Create IntelliJ IDEA modules for aggregator projects，为多模块Maven工程的每个模块分别创建IDEA模块</li>
<li>勾选Create module groups for multi-module Maven projects，自动为多模块Maven工程创建模块分组</li>
</ol>
<p>点击下一步，选择合适的Maven profile，再次下一步，出现类似如下界面：</p>
<p><img class="aligncenter size-full wp-image-11888" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Import-Module_002.png" alt="Import Module_002" width="100%" /></p>
<p>该界面列出所有待导入的Maven工程的列表，注意，对于多模块Maven工程，只有<span style="background-color: #c0c0c0;">容器工程（packaging=pom）才会出现在此列表中</span>。</p>
<p>点击Next，选择IDEA工程使用的Project SDK，保持和Maven工程的JDK版本要求一致，然后点击Finish完成导入。我多次尝试，设置Project SDK没有效果，必须手工到Project Structure ⇨ Project Settings ⇨ Project中设置，在这个工程中，需要设置Project SDK为1.6、Project language level为6。</p>
<div class="blog_h2"><span class="graybg">IDEA工程结构</span></div>
<p>导入完毕后，在Project工具窗口中，可以看到如下的IDEA模块（组）：</p>
<p><img class="aligncenter size-full wp-image-11895" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_0041.png" alt="Selection_004" width="549" height="821" /></p>
<p>可以看到，IDEA对pems、security-monitoring两个Maven工程创建了模块分组，包含模块配置的*.iml文件存放在IDEA工程的根目录下。</p>
<p>打开Project Structure ⇨ Modules，可以看到IDEA成功识别到工程使用了AspectJ，并且自动配置了AspectJ Facet：</p>
<p><img class="aligncenter size-full wp-image-11897" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Project-Structure_002.png" alt="Project Structure_002" width="100%" /></p>
<p>最好在Settings ⇨ B,E,D ⇨ Java Compile设置一下IDEA的ajc的Command line parameters，和aspectj-maven-plugin保持一致，例如</p>
<pre class="crayon-plain-tag">-showWeaveInfo -XnoInline -outxml -Xlint:ignore -1.6 </pre>
<p>打开Project Structure ⇨ Artifacts，可以看到IDEA自动处理了Overlay：</p>
<p><img class="aligncenter size-large wp-image-11900" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Project-Structure_003-1024x757.png" alt="Project Structure_003" width="100%" /></p>
<p>打开Settings ⇨ B,E,D ⇨ Compiler ⇨ Java Compiler，可以看到编译器被设置为Ajc。尽管我们在pom.xml中并没有指定对aspectjtools的依赖，但是IDEA很聪明的猜测到了它的位置。</p>
<p>需要注意的是，IDEA<span style="color: #ffffff;"><strong><span style="background-color: #ff0000;">完全是依据POM中的依赖列表来识别AspectJ和Overlay</span></strong></span>，与aspectj-maven-plugin、maven-war-plugin插件的配置无关。IDEA不会（像Eclipse的m2e Connector那样）识别这些插件进而改变IDEA工程模型</p>
<div class="blog_h2"><span class="graybg">解决问题</span></div>
<div class="blog_h3"><span class="graybg">overlay的excludes支持</span></div>
<p>上面我们提到过，IDEA可以成功识别WAR的overlays，但是它却不支持maven-war-plugin的excludes规则：</p>
<pre class="crayon-plain-tag">&lt;overlay&gt;
    &lt;groupId&gt;com.kingsmartsi&lt;/groupId&gt;
    &lt;artifactId&gt;pems-rpt&lt;/artifactId&gt;
    &lt;excludes&gt;
        &lt;exclude&gt;WEB-INF/lib/sshe-utils-*.jar&lt;/exclude&gt;
        &lt;exclude&gt;WEB-INF/lib/security-monitoring-core-*.jar&lt;/exclude&gt;
        &lt;exclude&gt;WEB-INF/lib/comm-tools-*.jar&lt;/exclude&gt;
        &lt;exclude&gt;WEB-INF/lib/commons-lang-*.jar&lt;/exclude&gt;
        &lt;exclude&gt;WEB-INF/lib/commons-logging-*.jar&lt;/exclude&gt;
    &lt;/excludes&gt;
&lt;/overlay&gt;</pre>
<p>这导致<span style="background-color: #c0c0c0;">本来需要排除掉的内容进入需要部署war构件</span>，例如上面的pems-web-manager:war exploded。要解决此问题，你可以<span style="background-color: #c0c0c0;">逐个修改参与overlay的war</span>构件，将不需要的内容从它本身的输出列表中排除，例如：</p>
<p><img class="aligncenter size-full wp-image-11911" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Menu_002.png" alt="Menu_002" width="100%" /></p>
<p>排除掉某些JAR包后，IDEA会作为错误看待，并且显示在Project Structure 左侧面板的“Problems”中，构件上也被打上波浪线提示。要去除这些提示很简单，只需要把这些构件的Type改为“Other”即可：</p>
<p><img class="aligncenter size-full wp-image-11915" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Project-Structure_001.png" alt="Project Structure_001" width="100%" /></p>
<p>你可能发现这种处理方式太过繁琐，下面有两种备选方案：</p>
<ol>
<li>运行Tomcat前，调用maven-war-plugin插件的Goal生成pems-web-manager:war exploded构件的目录内容。这样就可以遵循excludes规则，后续的Build Artifacts操作也不会破坏此规则。默认的IDEA在启动Tomcat前自动执行Build Artifacts操作</li>
<li>不使用maven-war-plugin的overlay的excludes功能。以该工程为例，excludes的目的是排除掉低版本的commons-lang:1.0，这完全可以（也应当）用Maven传递性依赖排除机制来实现</li>
</ol>
<div class="blog_h3"><span class="graybg">找不到@AnyMetaDef的问题</span></div>
<p>这个比较奇葩，工程内有两个Hibernate实体类，引用了同一个<pre class="crayon-plain-tag">@AnyMetaDef(name="device")</pre> 注解。之前我们为了减少重复代码，让这两个类共享一个@AnyMetaDef，即：在其中一个类上定义@AnyMetaDef，另外一个类仅仅通过<pre class="crayon-plain-tag">@Any ( metaDef = "device" )</pre> 引用。</p>
<p>现在的问题是，在Eclipse下部署到Tomcat，@AnyMetaDef必须定义在A类上；在IDEA下部署到Tomcat，@AnyMetaDef必须定义在B类上。否则就会报错：Unable to find @AnyMetaDef for an @(ManyTo)Any mapping ...</p>
<p>看样子和类的加载顺序有关系，而这两个类的加载顺序受运行环境的影响。</p>
<p>最简单的解决办法就是重复定义一个@AnyMetaDef并指定不一样的名字，由于@Any很少用到，这不会导致过分的维护压力。</p>
<div class="blog_h3"><span class="graybg">JavaScript智能提示缓慢</span></div>
<p>当工程内包含大量JavaScript时，会验证增加IDEA分析代码的压力，此时你可以把不需要参与搜索、代码分析的：</p>
<ol>
<li>目录 Mark Directory As ⇨ Excluded</li>
<li>JS文件 Mark as Plain Text</li>
</ol>
<p>但是要注意，<span style="background-color: #c0c0c0;">Excluded的目录不会发布到Tomcat服务器</span>，可能会导致404错误。 </p>
<div class="blog_h3"><span class="graybg">创建新的Maven模块</span></div>
<p>最好<a href="/maven-faq#new-maven-module">通过命令行来添加</a>，通过IDEA的GUI添加可能出现问题，取决于项目的组织方式。</p>
<div class="blog_h2"><span class="graybg">部署和运行</span></div>
<p>参考<a href="#webapp-dev">Web应用开发</a>一节创建一个Tomcat服务器，把pems-web-manager:war exploded、sshe-static:war exploded两个构件添加到Run/Debug Configurations的Deployment选项卡，然后在Server选项卡设置合适的参数：</p>
<p><img class="aligncenter size-full wp-image-11939" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Run-Debug-Configurations_002.png" alt="Run-Debug Configurations_002" width="100%" /></p>
<p>设置完毕后点击确定，在工具栏选择此Run/Debug Config，点击<img class="aligncenter size-full wp-image-11871 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/debug.png" alt="debug" width="16" height="16" />或<img class="aligncenter size-full wp-image-11878 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/execute.png" alt="execute" width="16" height="16" />即可启动Tomcat。</p>
<p>按照上图的配置，运行期间发生<span style="background-color: #c0c0c0;">修改（或新添加）</span>的类、资源文件会在切<span style="background-color: #c0c0c0;">换到其它窗口后、Update操作后</span>（Ctrl + F10）自动同步到Tomcat服务器，同步后IDEA会在Run/Debug工具窗口上方弹出类似下面的气泡提示：<img class="aligncenter size-full wp-image-11943 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_003.png" alt="Selection_003" width="331" height="25" /></p>
<div class="blog_h2"><span class="graybg">打包</span></div>
<p>应当使用Maven来打包，首先为所有顶级Maven工程创建Run/Debug Config，Command line可以设置为<pre class="crayon-plain-tag">clean install -Dmaven.test.skip=true</pre> ，然后创建一个额外的Confg调用它们。点击Before launch下面的<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" /> ⇨ Run Another Configuration，调用pems依赖的所有Maven工程的的打包命令：</p>
<p><img class="aligncenter size-full wp-image-11990" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Run-Debug-Configurations_001.png" alt="Run-Debug Configurations_001" width="100%" /></p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">无法连接到Maven仓库</span></div>
<p>IntelliJ IDEA本身的网络代理配置，不会影响到Maven命令的执行，因此，你需要配置Maven代理。参见：<a href="/maven-faq#use-proxy">Maven知识集锦</a></p>
<div class="blog_h2"><span class="graybg">运行Tomcat时把构件部署在哪</span></div>
<p>IDEA不会拷贝构件到其它目录，直接使用out/target目录下构建好的目录或者war包。</p>
<p>以Maven工程为例，如果使用exploded构件，那么：</p>
<ol>
<li>一般方式启动Tomcat：使用/target/artifactid-version/中的类和Web资源。目录artifactid-version由Build Artifacts动作生成</li>
<li>JRebel方式启动Tomcat：使用/target/classes中的类、使用/src/main/webapp下的Web资源</li>
</ol>
<div class="blog_h2"><span class="graybg">删除文件后Tomcat不同步</span></div>
<p>如果你把资源文件存放在src/main/java下，那么删除这些资源文件后，exploded构件目录不会同步。放在src/main/resources下则没有此问题。</p>
<div class="blog_h2"><span class="graybg">创建Maven项目无法获得原型列表</span></div>
<p>一直显示：loading archetype list，但是无法加载完毕。</p>
<p>解决办法：</p>
<ol>
<li>尝试删除/home/alex/.IntelliJIdeaxxxx.xx/system/maven目录后重试</li>
<li>尝试修改Preferences ⇨ Build Tools ⇨ Maven ⇨ Importing，修改 VM options for importer 为-Xmx1024m后重试</li>
</ol>
<div class="blog_h2"><span class="graybg">编译时织入Debug无法看到本地变量</span></div>
<p>例如和Spring联用时，调试注解了@Transactional的方法的时候，Debug工具窗口看不到本地变量的信息。</p>
<p>解决办法： Settings ⇨ B,E,D  ⇨ Compiler  ⇨ Java Compiler，设置Ajc Options / Command line parameters，添加<pre class="crayon-plain-tag">-preserveAllLocals</pre></p>
<p>此外还有提示 'this' is not available的情况，可以改用<pre class="crayon-plain-tag">ajc$this</pre>代替this。</p>
<div class="blog_h2"><span class="graybg">和Docker集成时如何配置容器</span></div>
<p>目前很多Docker的CLI选项Docker Integration插件不识别。手工编写的JSON file示例如下：</p>
<pre class="crayon-plain-tag">{
    "HostConfig": {
        "NetworkMode": "local",
        "Dns": [
            "172.21.0.1",
            "178.79.131.110",
            "223.6.6.6"
        ]
    },
    "NetworkingConfig": {
        "EndpointsConfig": {
            "local": {
                "Gateway": "172.21.0.99"
            }
        }
    }
}</pre>
<p>上面的例子示例了如何配置容器的网络参数，注意MAC、IP地址参考docker inspect的结果或者<a href="https://github.com/docker-java/docker-java/blob/master/src/main/java/com/github/dockerjava/api/model/ContainerNetwork.java">docker-java</a>进行设置没有效果。 </p>
<div class="blog_h2"><span class="graybg">修改CVS日志时间格式</span></div>
<p>要使用yyyy-MM-dd HH:mm格式的时间，为Intellij提供环境变量</p>
<pre class="crayon-plain-tag">export LC_TIME=sv_SE</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/intellij-idea-faq">IntelliJ IDEA知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/intellij-idea-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CLion知识集锦</title>
		<link>https://blog.gmem.cc/clion-faq</link>
		<comments>https://blog.gmem.cc/clion-faq#comments</comments>
		<pubDate>Wed, 13 Jan 2016 03:55:09 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11699</guid>
		<description><![CDATA[<p>简介 CLion是基于Intellij平台的IDE，主要用于C/C++开发。它使用CMake工程模型，你对CMakeLists.txt的任何更改都会反映到IDE中，CLion调用CMake的命令行完成工程构建。 基础 全局设置 设置路径 说明 B,E,D ⇨ Toolchains 工具链设置，可以指定CMake、GDB的路径，在Windows下你还可以指定MinGW或者Cygwin的安装位置 B,E,D ⇨ CMake 勾选Automatically Reload CMake project on editing Generation CMake Options，指定传递给cmake命令行的选项，例如通过[crayon-69dc3b0cadb7f518384773-i/] 传递变量值 <a class="read-more" href="https://blog.gmem.cc/clion-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/clion-faq">CLion知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>CLion是基于Intellij平台的IDE，主要用于C/C++开发。它使用CMake工程模型，你对CMakeLists.txt的任何更改都会反映到IDE中，CLion调用CMake的命令行完成工程构建。</p>
<div class="blog_h1"><span class="graybg">基础</span></div>
<div class="blog_h2"><span class="graybg">全局设置</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">设置路径</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>B,E,D ⇨ Toolchains</td>
<td>工具链设置，可以指定CMake、GDB的路径，在Windows下你还可以指定MinGW或者Cygwin的安装位置</td>
</tr>
<tr>
<td>B,E,D ⇨ CMake</td>
<td>
<p>勾选Automatically Reload CMake project on editing</p>
<p><span style="background-color: #c0c0c0;">Generation</span></p>
<p>CMake Options，指定传递给cmake命令行的选项，例如通过<pre class="crayon-plain-tag">-Dvar=name</pre> 传递变量值</p>
<p>展开Pass System Environment，可以覆盖系统环境变量。勾选Pass system environment variables可以传递系统环境变量到CMake的Generation阶段</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">和CMake协同</span></div>
<p>当通过CLion创建新工程时，CMakeLists.txt会自动创建。当工程结构复杂时，你可以手工为子目录创建CMakeLists.txt文件。</p>
<p>当你打开不是基于CMake的工程时，CLion允许做的事情很少——不能编辑代码、不能构建或运行。这种情况下你可以使用CMake的导入功能或者手工创建CMakeLists.txt文件。</p>
<p>手工修改CMakeLists.txt后，需要Reload工程才能体现出修改。当需要Reload时CMake工具窗口会有提示。你还可以启用Auto-Reload。</p>
<div class="blog_h3"><span class="graybg">重置CMake缓存</span></div>
<p>如果需要在构建前清空CMake的缓存条目，可以定位到CMake工具窗口 ⇨ Cache选项卡，点击<img class="aligncenter size-full wp-image-11786 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_006.png" alt="Selection_006" width="19" height="19" />按钮。</p>
<div class="blog_h3"><span class="graybg">修改构建配置</span></div>
<p>点击<img class="aligncenter size-full wp-image-11787 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Selection_007.png" alt="Selection_007" width="98" height="21" />右侧的向下箭头，选择Edit Configurations，可以修改某个条目使用的构建配置，默认支持CMake的四种构建配置。</p>
<p>你可以在CMakeLists.txt中添加自定义的构建配置：</p>
<pre class="crayon-plain-tag"># 必须是第一个变量定义
set(CMAKE_CONFIGURATION_TYPES "CustomType1;CustomType2" CACHE STRING "" FORCE)</pre>
<div class="blog_h3"><span class="graybg">修改工程根目录</span></div>
<p>打开既有CMake工程或者创建新工程后，默认的CLion把CMakeLists.txt所在目录作为工程根目录（Project root directory）看待，要改变此行为，可以Tools ⇨ CMake ⇨ Change Project Root。</p>
<div class="blog_h3"><span class="graybg">CMakeLists.txt基本命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>include_directories</td>
<td>
<p>指定头文件的搜索位置。头文件不但对编译是必须的，也可以被CLion索引，以提供代码自动完成、代码导航</p>
<p>依据操作系统的不同，编译器会自动搜索一些预定义的位置，你可以可以手工添加：</p>
<pre class="crayon-plain-tag"># 可选的BEFORE/AFTER关键字用于控制搜索顺序
include_directories(BEFORE ${MY_SOURCE_DIR}/src )</pre>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>set</td>
<td>设置一系列变量的值：<br />
<pre class="crayon-plain-tag"># 设置C编译器的位置
set (CMAKE_CXX_COMPILER, "C:\\MinGW\\bin\\g++")
# 启用C99标准
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
# 启用C++ 11标准
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# 启用警告
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")</pre>
</td>
</tr>
<tr>
<td>add_executable</td>
<td>添加可执行的构建目标</td>
</tr>
<tr>
<td>add_library</td>
<td>添加库：<br />
<pre class="crayon-plain-tag">add_library (my_library STATIC|SHARED|MODULE ${SOURCE_FILES})</pre>
</td>
</tr>
<tr>
<td>target_link_libraries</td>
<td>包含链接所需的库：<br />
<pre class="crayon-plain-tag"># 使用BOOST库
find_package(Boost)
IF (Boost_FOUND)
    include_directories(${Boost_INCLUDE_DIR})
endif()
set (Boost_USE_STATIC_LIBS OFF) # enable dynamic linking
set (Boost_USE_MULTITHREAD ON)  # enable multithreading
find_package (Boost COMPONENTS REQUIRED chrono filesystem)
# 声明链接到BOOST库
target_link_libraries (my_target ${Boost_LIBRARIES})</pre>
</td>
</tr>
<tr>
<td>add_subdirectory</td>
<td>
<p>用于包含子工程。</p>
<p>一个工程可以依赖于其它工程，CMake没有类似于VS的解决方案（Solution）的概念，但是它允许你手工定义工程之间的依赖关系</p>
<p>典型的，你希望在工作区中这样管理多工程（Multi-project）结构：</p>
<ol>
<li>打开主工程时，其依赖的工程一并打开</li>
<li>主工程的设置自动应用到被依赖的工程</li>
<li>重构、代码完成等可以影响到所有工程</li>
</ol>
<p>上面的三点需求可以通过合适的CMakeList.txt完成，你需要把上述<span style="background-color: #c0c0c0;">所有工程组织到CMakeList.txt所在目录之下</span>，形成树形结构，每个子目录对应一个子工程，并且子目录有自己的CMakeLists.txt。最后，在根目录的CMakeLists.txt中添加：</p>
<pre class="crayon-plain-tag">add_subdirectory (project1) # 把project1包含到主工程
add_subdirectory (project2) # 把project2包含到主工程</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"><a id="debug"></a>调试</span></div>
<div class="blog_h2"><span class="graybg">本地调试</span></div>
<div class="blog_h3"><span class="graybg">Attach到进程</span></div>
<p>点击 Run ⇨ Attach to Process... 可以调试运行中的进程。当Debug工具窗口提示Debugger attached to process ...后，<span style="background-color: #c0c0c0;">点击左侧的Pause Program按钮，这时GDB窗格支持输入</span>。
<p>你可能需要设置源码搜索路径，否则设置断点时GDB窗格提示No source file named /home/alex/CPP/project ...要设置源码搜索路径，在GDB窗格中输入set directories命令，示例：</p>
<pre class="crayon-plain-tag">set directories /home/alex/CPP/projects/clion/envoy</pre>
<div class="blog_h3"><span class="graybg">启动并调试程序</span></div>
<p>你可以启动任意应用程序并调试，此程序不一定是CLion通过Cmake构建出来的。</p>
<p>点击 Run ⇨ Edit Configurations，添加一个Application类型的配置，Executable选择你需要调试的应用程序的绝对路径：</p>
<p><img class="aligncenter  wp-image-25049" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Run-Debug-Configurations.png" alt="run-debug-configurations" width="1012" height="645" /></p>
<p>注意：把Before launch下面的Build项去掉。</p>
<p>然后点击调试按钮即可。</p>
<div class="blog_h2"><span class="graybg">远程调试</span></div>
<div class="blog_h3"><span class="graybg">启动gdbserver</span></div>
<p>脚本示例：</p>
<pre class="crayon-plain-tag">#!/usr/bin/env bash
gdbserver --once localhost:2345 envoy ...</pre>
<div class="blog_h3"><span class="graybg">Clion配置</span></div>
<p>配置示例截图：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2016/01/Run-Debug-Configuration-Remote.png"><img class="aligncenter size-full wp-image-25305" src="https://blog.gmem.cc/wp-content/uploads/2016/01/Run-Debug-Configuration-Remote.png" alt="run-debug-configuration-remote" width="933" height="635" /></a></p>
<div class="blog_h2"><span class="graybg">路径映射</span></div>
<p>基于gdbserver进行远程调试时，特别需要注意路径映射的问题。</p>
<p>Remote端使用（符号表中的）相对路径貌似无效，确定决定路径，可以按照如下步骤：</p>
<ol>
<li>使用gdb连接到gdbserver：<br />
<pre class="crayon-plain-tag">(gdb) target remote localhost:2345</pre>
</li>
<li>
<p>打印源码列表：</p>
<pre class="crayon-plain-tag">(gdb) info sources
/proc/self/cwd/source/exe/main_common.cc ...
/proc/self/cwd/bazel-out/k8-dbg/bin/include/envoy/common ...
/home/alex/.cache/bazel/_bazel_alex/3a80e9c345550f05d77beb52ded4f5f3/external/envoy_deps_cache_2c744dffd279d7e9e0910ce594eb4f4f/libevent.dep.build/libevent-release-2.1.8-stable/build/../ratelim-internal.h</pre>
</li>
<li>
<p>通过打印当前栈追踪，查看符号表中的源码路径：
<pre class="crayon-plain-tag">(gdb) where
#0  epoll_wait (epfd=&lt;optimized out&gt;, events=events@entry=0x2e00a80, maxevents=&lt;optimized out&gt;, timeout=timeout@entry=100) at ../epoll_sub.c:66
#1  0x00000000010b7709 in epoll_dispatch (base=0x2e70000, tv=&lt;optimized out&gt;) at ../epoll.c:462
#2  0x00000000010ad8dd in event_base_loop (base=0x2e70000, flags=0) at ../event.c:1947
#3  0x0000000000a5067c in Envoy::Event::DispatcherImpl::run (this=0x2d9d3f0, type=Envoy::Event::Dispatcher::RunType::Block) at source/common/event/dispatcher_impl.cc:165
#4  0x00000000009d74c5 in Envoy::Server::InstanceImpl::run (this=0x2e2d200) at source/server/server.cc:466
#5  0x0000000000425baa in Envoy::MainCommonBase::run (this=0x2e2c8b0) at source/exe/main_common.cc:103
#6  0x000000000040cd68 in Envoy::MainCommon::run (this=0x2e2c480) at bazel-out/k8-dbg/bin/source/exe/_virtual_includes/envoy_main_common_lib/exe/main_common.h:86
#7  0x000000000040a4bf in main (argc=19, argv=0x7fffffffd238) at source/exe/main.cc:37 </pre>
</li>
<li>
<p> 可以看到source目录对应到的是/proc/self/cwd/source目录。假设Debugger端的源码路径是/home/alex/CPP/projects/clion/envoy/source，则需要建立两者的路径映射
</li>
</ol>
<div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">如何执行安装</span></div>
<p>CLion默认指定的构建树的位置，可以在Messages窗口看到，通过CLion执行一次构建，可以看到类似：</p>
<pre class="crayon-plain-tag">bin/cmake --build /home/alex/.CLion2016.1/system/cmake/generated/sds-1b689bf9/1b689bf9/Debug --target sds -- -j 8</pre>
<p>的消息，你只需要CD到<pre class="crayon-plain-tag">--build</pre> 后面跟着的目录，就可以执行<pre class="crayon-plain-tag">make install</pre> 进行安装了，当然前提是在CMakeLists.txt中声明合适的install命令。</p>
<div class="blog_h3"><span class="graybg">调试时对象无法展开</span></div>
<p>也无法调用对象的方法、查看其字段值。</p>
<p>出现此问题的原因是，目标对象在编译时没有开启调试信息。如果使用GCC，尝试添加编译参数-O0 -g3。 </p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/clion-faq">CLion知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/clion-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IntelliJ平台知识集锦</title>
		<link>https://blog.gmem.cc/intellij-faq</link>
		<comments>https://blog.gmem.cc/intellij-faq#comments</comments>
		<pubDate>Fri, 30 Oct 2015 08:21:46 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Work]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11467</guid>
		<description><![CDATA[<p>特性简介 IntelliJ是JetBrains公司的一个基础软件平台，主要用于构建IDE。IDEA、WebStorm、PHPStorm、CLion、PyCharm、RubyMine、DataGrip、Android Studio等IDE均基于此平台构建。本文记录通用于这些IDE的知识。  工具窗口 IntelliJ中在编辑器左侧、下面、右侧的若干小面板，被称为工具窗口。工具窗口名字前面可能有N: 前缀，你可以使用Alt + N快速切换到此工具窗口。 大部分工具窗口支持快速搜索（Speed Search）功能，你只需要聚焦目标工具窗口，然后直接输入你需要搜索的内容，即可定位到匹配条目。 视图模式 IntelliJ引入视图模式这一特性，允许IDE以不同方式呈现： 视图模式 说明 Full Screen mode 全屏模式，占据最大的空间。操作系统任务栏、菜单栏等自动隐藏 Presentation mode 演示模式，类似于全屏模式，字体很大，用于投影展示 Distraction-free <a class="read-more" href="https://blog.gmem.cc/intellij-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/intellij-faq">IntelliJ平台知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">特性简介</span></div>
<p>IntelliJ是JetBrains公司的一个基础软件平台，主要用于构建IDE。IDEA、WebStorm、PHPStorm、CLion、PyCharm、RubyMine、DataGrip、Android Studio等IDE均基于此平台构建。本文记录通用于这些IDE的知识。 </p>
<div class="blog_h2"><span class="graybg">工具窗口</span></div>
<p>IntelliJ中在编辑器左侧、下面、右侧的若干小面板，被称为工具窗口。工具窗口名字前面可能有N: 前缀，你可以使用Alt + N快速切换到此工具窗口。</p>
<p>大部分工具窗口支持<span style="background-color: #c0c0c0;">快速搜索（Speed Search）</span>功能，你只需要聚焦目标工具窗口，然后直接输入你需要搜索的内容，即可定位到匹配条目。</p>
<div class="blog_h2"><span class="graybg">视图模式</span></div>
<p>IntelliJ引入视图模式这一特性，允许IDE以不同方式呈现：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">视图模式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Full Screen mode</td>
<td>全屏模式，占据最大的空间。操作系统任务栏、菜单栏等自动隐藏</td>
</tr>
<tr>
<td>Presentation mode</td>
<td>演示模式，类似于全屏模式，字体很大，用于投影展示</td>
</tr>
<tr>
<td>Distraction-free mode</td>
<td>免打扰模式，仅仅居中显示代码，工具窗口、工具栏、编辑器标签都隐藏</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">自动完成</span></div>
<p>IntelliJ支持比较智能化的代码提示（Code Assist），在你输入的时候匹配的候选代码提示会自动出现。匹配方式不仅仅限于前缀，不连续的字符也可以用于筛选匹配，例如输入mtm可以匹配myTestMethod方法。</p>
<p>对于JavaScript等动态语言，IntelliJ可以分析得到工程中所有的变量、函数名称，用于代码提示，而不考虑代码的上下文，例如：</p>
<pre class="crayon-plain-tag">var cmp = null;
// 输入到cmp.init，IDEA即可给出initRenderData提示，这是因为在工程的某个文件中定义了initRenderData()方法
// 尽管从山下文判断，此提示是不正确的
cmp.initRenderData();</pre>
<p>这种提示方式很有效，特别是在记不清函数名称的时候，但是工程中代码过多时，IntelliJ会消耗大量时间分析代码，造成卡顿。 </p>
<p>IntelliJ支持独特的<span style="background-color: #c0c0c0;">Postfix自动完成</span>，你可以输入<pre class="crayon-plain-tag">false.if</pre> 来得到<pre class="crayon-plain-tag">if(false){}</pre> 。</p>
<div class="blog_h2"><span class="graybg">LiveTemplate</span></div>
<p>IntelliJ支持自定义模板代码，这些代码可以插入到当前编辑器中，或者环绕当前编辑器中被选中的代码。</p>
<p>要自定义LiveTemplate，可以Settings ⇨ Editor ⇨ Live Templates，然后点击右侧的<img class="aligncenter size-full wp-image-11866 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/01/add.png" alt="add" width="16" height="16" />按钮添加新的模板。你可以为模板设置一个<span style="background-color: #c0c0c0;">缩写</span>，这样，在编辑器里输入缩写后，按Tab即可自动完成。</p>
<p>要插入模板，可以使用Intellij的智能提示，或者使用快捷键：Ctrl + J、Ctrl + Alt + J。</p>
<div class="blog_h3"><span class="graybg">模板变量</span></div>
<p>预定义变量：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$SELECTION$</td>
<td>代表当前编辑器的选区</td>
</tr>
<tr>
<td>$END$</td>
<td>代表光标位置，在插入模板后，光标将停留于此</td>
</tr>
</tbody>
</table>
<p>Live template的强大之处在于，它允许你自定义模板变量，变量的值可以从IntelliJ提供的大量函数中推导出。这样，你就可以很方便的在模板在插入上下文信息，例如类名、方法名、当前时间等信息。例如下面的Live template，它可以向当前Java类中插入日志记录器的声明语句：</p>
<p><img class="aligncenter size-full wp-image-13240" src="https://blog.gmem.cc/wp-content/uploads/2015/10/livetemplate-with-var.png" alt="livetemplate-with-var" width="674" height="535" /></p>
<div class="blog_h1"><span class="graybg">全局设置</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">设置路径</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>A&amp;B ⇨ Apperance</td>
<td>
<p><span style="background-color: #c0c0c0;">UI Options</span><br />Theme：Ubuntu下选择GTK+，Wndows下选择Intellij<br />Override default fonts by：Ubuntu下选择Ubuntu/14，Wndows下选择Segoe UI/12<br /><span style="background-color: #c0c0c0;">Window Options</span><br />勾选Show memory indicator、WdeScreen tool window layout</p>
</td>
</tr>
<tr>
<td>A&amp;B ⇨ System Settings</td>
<td><span style="background-color: #c0c0c0;">Synchronization</span><br />勾选Save files automatically if application idle for 15 sec <br /><span style="background-color: #c0c0c0;">⇨ Updates</span><br />取消勾选 Automatically check updates for ..<br /><span style="background-color: #c0c0c0;">⇨ Usage stat.</span><br />取消勾选 Allow sending usage statistics to JetBrains</td>
</tr>
<tr>
<td>A&amp;B ⇨ Path Variables</td>
<td>MAVEN_REPOSITORY<br />Ubuntu下设置为~/JavaEE/maven/maven_repository<br />Windows下设置为D:\JavaEE\maven\maven_repository</td>
</tr>
<tr>
<td>Keymap</td>
<td>
<p>考虑到需要使用多套IDE、操作系统，过度差异的快捷键容易造成记忆混乱，因此选择Keymaps为Eclipse，并在其基础上做调整<br />清除以下快捷键：<br />Ctrl + F6、Ctrl + Shift + F6、Ctrl + F9、Ctrl + Shift + F8、Alt + Shift + F9、Alt + Shift + F10、Ctrl + W、Ctrl + Shift + W、Alt + Shift + X、Alt + Shift + D、Alt + Shift + Z</p>
<p>添加以下快捷键：<br />Class Name Completion：添加Alt + /<br />Main Menu ⇨ Force Step Over：添加 Shift + F6<br />Main Menu ⇨ Force Step Into：添加 Shift + F5<br />Main Menu ⇨ Force Run to Cursor：改为Ctrl + Alt + R<br />Main Menu ⇨ Run ⇨ Debug：改为F9<br />Main Menu ⇨ Run ⇨ Run：改为F10<br />Main Menu ⇨ Run ⇨ Debug...：改为Shift + F9<br />Main Menu ⇨ Run ⇨ Run...：改为Shift + F10<br />Main Menu ⇨ Code ⇨ Insert Live Template：添加 Ctrl + J<br />Main Menu ⇨ Code ⇨ Completion ⇨ Basic：改为Alt + Space<br />Main Menu ⇨ View ⇨ Parameter Info：添加Alt + P<br />Other ⇨ Search Everywhere：添加Ctrl + Shift + Q<br />Editor Actions ⇨ Extend Selection：添加Ctrl + W<br />Editor Actions ⇨ Shrink Extend Selection：添加Ctrl + Shift + W</p>
</td>
</tr>
<tr>
<td>Editor ⇨ General</td>
<td>
<p><span style="background-color: #c0c0c0;">Other</span><br />勾选Show quick documentation on mouse move</p>
<p><span style="background-color: #c0c0c0;">Appearance</span><br />勾选Show line numbers<br />取消勾选Show right margin<br />取消勾选Show vertical indent guides</p>
<p><span style="background-color: #c0c0c0;">Code Completion</span><br />勾选Autopopup documentation in(ms)</p>
</td>
</tr>
<tr>
<td>Editor ⇨ Colors &amp; Fonts</td>
<td>
<p>Scheme另存以便修改<br /><span style="background-color: #c0c0c0;">Font</span><br />Primary font：Ubuntu下选择Ubuntu mono/16；Windows下选择Consolas/14 <br />Line spacing： 选择1.1<br />Secondary font：Windows下选择XCross Mono.clear<br /><span style="background-color: #c0c0c0;">Language Defaults</span><br />Comments中，把Block Comment、Line Comment、Doc Comment/Text的斜体勾选去掉<br /><span style="background-color: #c0c0c0;">Console Font</span><br />Primary font：Ubuntu下选择Ubuntu mono/14；Windows下选择Consolas/12<br />Secondary font：Windows下选择XCross Mono.clear</p>
<p>Ubuntu下使用GTK+主题时，参考下面设置<br /><span style="background-color: #c0c0c0;">General<br /></span>Editor ⇨ Selection Background，设置为F07746<br />Debugger ⇨ Execution point，取消Foreground，下面两个设置为C6DBAE、ADDB7C<br />Debugger ⇨ Inlined values for breakpointline，前景色设置为FFFFFF</p>
</td>
</tr>
<tr>
<td>Editor ⇨ Code Style</td>
<td>导入配置 intellij-code-style.xml</td>
</tr>
<tr>
<td>Editor ⇨ Inspections</td>
<td>取消勾选Spelling  ⇨ Typo </td>
</tr>
<tr>
<td>B,E,D  ⇨ Debugger</td>
<td>Build-in server Port设置为8800</td>
</tr>
<tr>
<td>B,E,D ⇨ Maven</td>
<td>
<p>Maven home directory：D:/JavaEE/maven/3.2.3<br />User settings file：D:\JavaEE\config\maven-settings.xml</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用快捷键</span></div>
<p>下表列出的快捷键，基于Eclipse方案修改得到：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">分类</td>
<td style="width: 25%; text-align: center;">快捷键</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;" rowspan="27"><b>工具和视图</b></td>
<td>Ctrl + Alt + S</td>
<td>弹出设置对话框  </td>
</tr>
<tr>
<td>Ctrl + Tab<br />Ctrl + Shift + Tab</td>
<td>弹出小窗格，在其中快速切换编辑器窗口和视图窗口</td>
</tr>
<tr>
<td>Win/Opt + R</td>
<td>弹出小窗格，显示最近打开的文件列表</td>
</tr>
<tr>
<td>Alt/Cmd + R</td>
<td>弹出小窗格，显示最近修改的文件列表</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + Left<br />Ctrl + Alt/Cmd + Right</td>
<td>在打开的编辑器Tab页之间切换</td>
</tr>
<tr>
<td>Alt/Cmd + Shift + Left </td>
<td>切换到上一个编辑位置</td>
</tr>
<tr>
<td>Alt/Cmd + Shift + Right </td>
<td>切换到下一个编辑位置</td>
</tr>
<tr>
<td>Alt/Cmd + Left<br />Alt/Cmd + Right</td>
<td>
<p>在编辑器Tab页之间切换</p>
<p>但是依据你针对编辑器页签的操作情况，可能在同一个编辑器页签中多次切换光标位置，而不是立即切换到上一个页签</p>
</td>
</tr>
<tr>
<td>Win/Opt + 0</td>
<td>Favorites工具窗口</td>
</tr>
<tr>
<td>Win/Opt + 1</td>
<td>Project工具窗口</td>
</tr>
<tr>
<td>Win/Opt + 2</td>
<td>Structure工具窗口</td>
</tr>
<tr>
<td>Win/Opt + 3</td>
<td>Hierarchy工具窗口</td>
</tr>
<tr>
<td>Win/Opt + 4</td>
<td>Maven Projects工具窗口</td>
</tr>
<tr>
<td>Win/Opt + 5</td>
<td>Database工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 0</td>
<td>Terminal工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 3</td>
<td>Event Log工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 4</td>
<td>Docker工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 5</td>
<td>Debug工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 6</td>
<td>Run工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 7</td>
<td>TODO工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + 8</td>
<td>Version Control工具窗口</td>
</tr>
<tr>
<td>Ctrl + Shift + ESC</td>
<td>关闭所有工具窗口</td>
</tr>
<tr>
<td>Ctrl + Esc</td>
<td>关闭当前活动工具窗口</td>
</tr>
<tr>
<td>Alt/Cmd + T,T</td>
<td>在Hierarchy工具窗口中显示类型的层次关系</td>
</tr>
<tr>
<td>Alt/Cmd + T,M</td>
<td>在Hierarchy工具窗口中显示方法覆盖的层次关系</td>
</tr>
<tr>
<td>Alt/Cmd + T,C</td>
<td>在Hierarchy工具窗口中显示方法调用的层次关系</td>
</tr>
<tr>
<td>Delete<br />Alt/Cmd + Backspace</td>
<td>删除文件</td>
</tr>
<tr>
<td style="text-align: center;" rowspan="25"><strong>构建/运行</strong></td>
<td>Ctrl + B</td>
<td>构建工程</td>
</tr>
<tr>
<td>Win/Opt + B</td>
<td>构建模块</td>
</tr>
<tr>
<td>Alt/Cmd + B</td>
<td>对于IDEA：构建构件<br />对于CLion：构建工程<br />对于AppCode：构建工程</td>
</tr>
<tr>
<td>Ctrl  + F10</td>
<td>运行当前文件（上下文配置）</td>
</tr>
<tr>
<td>Ctrl  + F9</td>
<td>调试当前文件（上下文配置）</td>
</tr>
<tr>
<td>Shift + F10</td>
<td>弹出菜单，选择一个文件并运行</td>
</tr>
<tr>
<td>Shift + F9</td>
<td>弹出菜单，选择一个文件并调试</td>
</tr>
<tr>
<td>F10</td>
<td>运行当前Run/Debug配置</td>
</tr>
<tr>
<td>F9</td>
<td>调试当前Run/Debug配置</td>
</tr>
<tr>
<td>F11</td>
<td>停止运行当前程序</td>
</tr>
<tr>
<td>F12</td>
<td>对于IDEA：更新当前Java应用<br />对于PhpStorm：上传到默认服务器</td>
</tr>
<tr>
<td>F5</td>
<td>Step Into，跳过Step过滤器中包含的库、构造器等代码</td>
</tr>
<tr>
<td>Shift + F5</td>
<td>强制Step Into，忽略Step过滤器的设置</td>
</tr>
<tr>
<td>Ctrl + F5</td>
<td>智能Step Into，允许你选择Step Into哪个方法</td>
</tr>
<tr>
<td>F6</td>
<td>Step Over</td>
</tr>
<tr>
<td>Shift + F6</td>
<td>强制Step Over，忽略任何断点</td>
</tr>
<tr>
<td>F7</td>
<td>Step Out</td>
</tr>
<tr>
<td>F8</td>
<td>释放单步跟踪，继续运行到下一断点</td>
</tr>
<tr>
<td>Win/Opt + Space</td>
<td>释放单步跟踪，运行到光标所在行</td>
</tr>
<tr>
<td>Ctrl + Win/Opt + Space</td>
<td>释放单步跟踪，强制运行到光标所在行，忽略任何断点</td>
</tr>
<tr>
<td>Alt/Cmd + E</td>
<td>调试模式下，临时估算一个表达式的值<br />要持续监测表达式，在Debug工具窗口中的Debugger  ⇨ Watches中添加表达式</td>
</tr>
<tr>
<td>Ctrl + Shift + B</td>
<td>添加/删除当前行的断点</td>
</tr>
<tr>
<td>Alt + Shift + B</td>
<td>启用/停用当前行的断点</td>
</tr>
<tr>
<td>Ctrl + Shift + M</td>
<td>启用/停用当前行的书签</td>
</tr>
<tr>
<td>Ctrl + W</td>
<td>关闭当前项目</td>
</tr>
<tr>
<td style="text-align: center;" rowspan="34"><strong>编辑器</strong></td>
<td>Alt/Cmd + G</td>
<td>生成新代码，例如添加构造器、getter/setter方法</td>
</tr>
<tr>
<td>Alt/Cmd + J</td>
<td>插入一个活动模板（Live Template） </td>
</tr>
<tr>
<td>Alt/Cmd + S,S</td>
<td>以...包围</td>
</tr>
<tr>
<td>Alt/Cmd + S,J</td>
<td>以活动模板包围</td>
</tr>
<tr>
<td>Shift + Enter</td>
<td>在当前行后面插入新行，并定位光标到新行</td>
</tr>
<tr>
<td>Ctrl + Shift + Enter</td>
<td>在当前行前面插入新行，并定位光标到新行</td>
</tr>
<tr>
<td>Ctrl + Enter</td>
<td>在光标所在位置后面插入换行符</td>
</tr>
<tr>
<td>Ctrl + Alt + Enter</td>
<td>完成当前语句，添加缺少的括号、分号等  </td>
</tr>
<tr>
<td>Alt/Cmd + O</td>
<td>快速大纲（当前文件结构）  </td>
</tr>
<tr>
<td>Ctrl + Alt + U</td>
<td>显示选中类/接口的类图（只显示超类）</td>
</tr>
<tr>
<td>Alt/Cmd + P</td>
<td>方法调用的参数类型提示</td>
</tr>
<tr>
<td>Alt/Cmd + Up<br />Alt/Cmd + Down</td>
<td>上下移动当前行</td>
</tr>
<tr>
<td>Alt/Cmd + D</td>
<td>删除当前行</td>
</tr>
<tr>
<td>Ctrl + /</td>
<td>注释/解除注释当前行</td>
</tr>
<tr>
<td>Ctrl + Shift + /</td>
<td>块注释</td>
</tr>
<tr>
<td>Alt/Cmd + +<br />Alt/Cmd + -</td>
<td>展开/折叠代码块</td>
</tr>
<tr>
<td>Ctrl + Alt + O</td>
<td>清理导入（Java的import、C的include等）</td>
</tr>
<tr>
<td>Ctrl + Shift + F</td>
<td>格式化当前文档</td>
</tr>
<tr>
<td>Ctrl + Shift + L</td>
<td>排序代码</td>
</tr>
<tr>
<td>Alt/Cmd + W<br />Alt/Cmd + Shift + W</td>
<td>扩展/收缩选区</td>
</tr>
<tr>
<td>Win/Opt + W</td>
<td>关闭当前编辑器窗口</td>
</tr>
<tr>
<td>Win/Opt + ,</td>
<td>上一个高亮错误</td>
</tr>
<tr>
<td>Win/Opt + .</td>
<td>下一个高亮错误</td>
</tr>
<tr>
<td>Alt/Cmd + ,</td>
<td>上一个编辑器内搜索匹配结果</td>
</tr>
<tr>
<td>Alt/Cmd + .</td>
<td>下一个编辑器内搜索匹配结果</td>
</tr>
<tr>
<td>Alt/Cmd + Shift + ,</td>
<td>上一个搜索匹配结果</td>
</tr>
<tr>
<td>Alt/Cmd + Shift + .</td>
<td>下一个搜索匹配结果</td>
</tr>
<tr>
<td>Ctrl  + Alt/Cmd + Up</td>
<td>上一个差异（文件对比弹框）</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + Dn</td>
<td>下一个差异（文件对比弹框）</td>
</tr>
<tr>
<td>Ctrl  + Alt/Cmd + Left</td>
<td>比较上一个文件</td>
</tr>
<tr>
<td>Ctrl  + Alt/Cmd + Right</td>
<td>比较下一个文件</td>
</tr>
<tr>
<td>Alt/Cmd + Mouse3</td>
<td>列选择模式</td>
</tr>
<tr>
<td>Ctrl + Shift + Up</td>
<td>上一个方法</td>
</tr>
<tr>
<td>Ctrl + Shift + Down</td>
<td>下一个方法</td>
</tr>
<tr>
<td style="text-align: center;" rowspan="11"><strong>搜索</strong></td>
<td>F3</td>
<td>
<p>转到类型、方法的声明</p>
<p>如果在方法声明上调用此快捷键，则寻找该方法的使用情况</p>
<p>备用快捷键： Ctrl  + Mouse1</p>
</td>
</tr>
<tr>
<td>Win/Opt + F</td>
<td>查找选中符号（字段、方法）的引用（调用）  </td>
</tr>
<tr>
<td>Win/Opt + Shift + F</td>
<td>查找选中符号在<span style="background-color: #c0c0c0;">当前文件</span>中的引用</td>
</tr>
<tr>
<td>Alt/Cmd + F</td>
<td>
<p>在当前文件中查找/替换</p>
<p>Alt/Cmd + , 上一个匹配<br />Alt/Cmd + . 下一个匹配</p>
</td>
</tr>
<tr>
<td>Ctrl + F</td>
<td>在选中的路径下查找包含内容的文件</td>
</tr>
<tr>
<td>Ctrl + R</td>
<td>在选中的路径下，查找包含内容的文件，并且替换这些内容</td>
</tr>
<tr>
<td>Ctrl + Shift + A</td>
<td>快速动作或者选项  </td>
</tr>
<tr>
<td>Ctrl + Shift + R</td>
<td>快速打开文件  </td>
</tr>
<tr>
<td>Ctrl + Shift + T</td>
<td>快速打开类  </td>
</tr>
<tr>
<td>Ctrl + Shift + Alt + N</td>
<td>快速定位到符号（方法、字段）</td>
</tr>
<tr>
<td>Ctrl + Shift + Q</td>
<td>Search Everywhere功能。可以依据文件名查找类、资源文件，可以查找符号名称、Action等（搜索结果是上面4个快捷键的集合）<br />默认键盘布局下是Shift, Shift  </td>
</tr>
<tr>
<td style="text-align: center;" rowspan="2">
<p><strong>智能化</strong></p>
</td>
<td>Alt/Cmd + Enter</td>
<td>意图操作（Intention Action），根据上下文猜测你可能想做的事情</td>
</tr>
<tr>
<td>Alt/Cmd + /</td>
<td>触发智能提示</td>
</tr>
<tr>
<td style="text-align: center;" rowspan="12"><strong>重构</strong></td>
<td>Ctrl + Shift + Alt/Cmd + R</td>
<td>弹出重构菜单</td>
</tr>
<tr>
<td>Alt/Cmd  + Delete</td>
<td>安全删除</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + R</td>
<td>重命名</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + S</td>
<td>改变方法签名</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + V</td>
<td>抽取局部变量</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + F</td>
<td>抽取字段</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + C</td>
<td>抽取常量</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + C</td>
<td>修改方法签名</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + M</td>
<td>抽取方法</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + P</td>
<td>抽取参数</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + L</td>
<td>转换为Lambda表达式</td>
</tr>
<tr>
<td>Shift + Alt/Cmd + I</td>
<td>将方法调用内联</td>
</tr>
<tr>
<td style="text-align: center;" rowspan="11"><strong>版本控制</strong></td>
<td>F4</td>
<td>显示上下文敏感的版本控制操作</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + U</td>
<td>更新工程、目录或者文件</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + A</td>
<td>添加选中文件到版本控制  </td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + Z</td>
<td>回退选中的文件</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + A</td>
<td>查看选定修订版影响的全部文件，你可以在弹出窗口中执行回退操作</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + V</td>
<td>查看选中目录/文件的VCS历史</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + P</td>
<td>Git Push操作</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + Shift + P</td>
<td>Gti Pull操作</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + /</td>
<td>Git Stash操作</td>
</tr>
<tr>
<td>Ctrl + Alt/Cmd + Shift + /</td>
<td>Git Unstash操作</td>
</tr>
<tr>
<td>Ctrl +Alt/Cmd + "</td>
<td>Git Branches</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">集成版本控制</span></div>
<div class="blog_h2"><span class="graybg">变更集</span></div>
<p>Change list是一个很体贴的功能，它允许你把纳入版本控制的文件分组，每次只提交其中的一组。某些配置文件需要针对本地环境修改，每个开发者都不一样。这些本地修改不能提交到VCS，这时可以把它们添加到另外一个变更集中，防止误提交。</p>
<p>在提交对话框中，左面板的又上角由一个Change list下拉框，你可以选择当前要提交的变更集：</p>
<p><img class="size-full wp-image-13152 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2015/10/change-list.png" alt="change-list" width="100%" /></p>
<p>左侧待提交文件列表中，你可以右击任何目录、文件，修改它们所属的变更集。</p>
<div class="blog_h2"><span class="graybg"><a id="work-with-github"></a>使用GitHub</span></div>
<div class="blog_h3"><span class="graybg">GitHub相关设置</span></div>
<p>Settings  ⇨  Version Control  ⇨ GitHub，在这里设置你的GitHub用户名和密码</p>
<p>Settings  ⇨  Version Control  ⇨ Git：</p>
<ol>
<li>Auto-update if push of the current branch was rejected：如果勾选，当Push失败后，会自动从远程仓库拉取更新</li>
<li>Allow force push：是否允许强制Push</li>
</ol>
<div class="blog_h3"><span class="graybg">分享当前项目到GitHub</span></div>
<p>VCS ⇨ Import into Version Control ⇨ Share Project on GitHub，弹出下面的对话框：</p>
<p><img class="size-full wp-image-12945 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2013/08/Share-Project-On-GitHub_002.png" alt="Share Project On GitHub_002" width="422" height="250" />点击Share后，IDE会使用你的账号，在GitHub上面创建一个空白的裸仓库。</p>
<p>在下一个弹出的对话框中，你可以选择需要添加的文件，并提交。</p>
<p>注意，<span style="background-color: #c0c0c0;">提交仅仅是针对你本地的Git仓库</span>。要Push到GitHub，需要VCS ⇨ Git ⇨ Push（Eclipse方案下快捷键为Ctrl + Shift + K） ：<img class="size-full wp-image-12950 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2015/10/Push-Commits_003.png" alt="Push Commits_003" width="651" height="563" /></p>
<p>可以看到，本次Push会在远程仓库初始化master分支。在后续的开发过程中，你随时可以把本地的Tag一同Push到远程仓库。</p>
<div class="blog_h3"><span class="graybg">Git Log解读</span></div>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-1.png"><img class="aligncenter size-full wp-image-19851" src="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-1.png" alt="git-log-1" width="905" height="165" /></a></p>
<p>上图中：</p>
<ol>
<li>当前位于develop分支上，看黄色标签</li>
<li>本地仓库的develop分支，和远程仓库的develop分支，处于同步状态，看绿色标签和紫色标签重合</li>
<li>本地仓库的master分支位于commit4（绿色标签），远程仓库的master分支位于commit5（紫色标签）</li>
</ol>
<p>在此时，你可以点击右下角 Git:develop ⇨ Remote Branches ⇨ Merge，<span style="background-color: #c0c0c0;">把任意分支合并到develop分支</span>。例如，合并master分支，之后Git Log变为：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-2.png"><img class="aligncenter size-full wp-image-19853" src="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-2.png" alt="git-log-2" width="904" height="214" /></a></p>
<p>可以看到，<span style="background-color: #c0c0c0;">合并后默认自动提交</span>。现在的情况是，本地仓库的develop和远程的develop不同步了。你可以执行一下Push：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-3.png"><img class="aligncenter size-full wp-image-19855" src="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-3.png" alt="git-log-3" width="905" height="214" /></a></p>
<p>这时develop分支的绿、紫标签又在一起了。</p>
<p>在develo commit 3（57a408d）上右击，Reset Current Branch to Here ... ⇨ Hard，现在变为：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-4.png"><img class="aligncenter size-full wp-image-19857" src="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-4.png" alt="git-log-4" width="906" height="185" /></a></p>
<p>注意黄色小标的位置。</p>
<p>我们可以强制Push版本57a408d到远程仓库，替换掉最新的673bb5c。在Push弹窗中，点击右下角Push按钮的下箭头，选择<span style="background-color: #c0c0c0;">Force Push</span>。提交后，服务器的develop分支的版本变的和本地一致：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-5.png"><img class="aligncenter  wp-image-19859" src="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-5.png" alt="git-log-5" width="909" height="166" /></a></p>
<p>其它开发人员，切换到develop分支后，执行Update Project，紫色标签的位置和远程仓库（被强制Push的）变的一致：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-6.png"><img class="aligncenter size-full wp-image-19865" src="https://blog.gmem.cc/wp-content/uploads/2015/10/git-log-6.png" alt="git-log-6" width="904" height="160" /></a> 这时，此开发人员比远程仓库多出来的develo commit 6，可以Push，也可以右键  ⇨ Undo Commit，<span style="background-color: #c0c0c0;">撤销掉提交。提交撤销后，其导致的修改将存在于当前工作区</span>。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">主题定制</span></div>
<p>这里提到的主题，是指IDE本身的组件（例如菜单、按钮）的样式风格。和编辑器色彩方案不同，Intellij平台没有提供细粒度的控制，只允许用户在三个预置的主题中选择。</p>
<p>最近Intellij平台下的IDE分别发布2016.1版本，升级后，我注意到Linux下的GTK+主题存在BUG，编辑器面板等地方出现多余灰色边框（<a href="https://youtrack.jetbrains.com/issue/IDEA-152424">IDEA-152424</a>），非常难看。等了几个月了，这个BUG还没有修复，为了拯救完美主义强迫症的自己，动手改吧……</p>
<p>我的想法是，把默认的Intellij主题中的蓝色调改为Ubuntu的橙色调风格，而不是去修复GTK+主题。这主要是考虑到默认主题在三大OS上都大量使用，JetBrains在此主题上花费的精力会比较多。相比之下，Linux下的GTK+主题由于用户较少，一直存在各种BUG。</p>
<p>虽然没研究过Intellij平台的源码，但是既然它是基于Swing的，我觉得应该找一些和LookAndFeel有关的类作为代码分析的入口。</p>
<p>我以CLion为例尝试。打开lib目录下最大的jar包，反编译，大概浏览了一下代码，发现com.intellij.ide.ui.laf包和主题相关，而IntelliJLaf类则对应了默认的IntelliJ主题。清空配置文件intellijlaf.properties的内容，启动CLion后发现完全界面完全变样了，由此推断，修改此配置文件是最简单的主题定制方式。修改并测试后，以下配置项被调整：</p>
<pre class="crayon-plain-tag"># 面板中选区的背景色
intellijlaf.selectionBackground=F07746
# 复选框边框，上半部分
CheckBox.darcula.borderColor1.selected=CC5627
# 复选框边框，下半部分
CheckBox.darcula.borderColor2.selected=CC5627
# 复选框背景，上半部分
CheckBox.darcula.backgroundColor1.selected=F58456
# 复选框背景，下半部分
CheckBox.darcula.backgroundColor2.selected=EC7B4D
# 选中复选框中的小钩子的阴影色
CheckBox.darcula.shadowColor.selected=90553C
# 获得焦点的复选框的颜色
CheckBox.darcula.focusedArmed.backgroundColor1.selected=F58456
CheckBox.darcula.focusedArmed.backgroundColor2.selected=EC7B4D
CheckBox.darcula.focused.backgroundColor1.selected=E7976D
CheckBox.darcula.focused.backgroundColor2.selected=E7976D
# 下拉列表箭头颜色
ComboBox.darcula.arrowFillColor=D96334
ComboBox.darcula.arrowFocusedFillColor=EC7B4D
# 按钮背景颜色
Button.darcula.selection.color1=EC7B4D
Button.darcula.selection.color2=CC5627
# 数字字段按钮背景色
Spinner.darcula.enabledButtonColor=D96334
# 树选区边框颜色
Tree.selectionBorderColor=D96334</pre>
<p>保存并更新压缩包后，重启CLion，可以发现一部分UI组件的配色改变了，但是聚焦的按钮、搜索框、复选框、单选框等组件依然会显示出蓝色。</p>
<p>其实切换到到深色主题Darcula，会发现那种蓝色也是存在的。进一步查看代码，可以发现IntelliJLaf的父类是DarculaLaf，原来浅色主题是从深色主题继承扩展而来的（比较反直觉…）。对某些控件加上蓝色发光效果，是DarculaLaf了类具有的一个行为，IntelliJLaf可能仅仅是继承了这一行为。</p>
<p>继续分析了一会代码，发现com.intellij.ide.ui.laf.darcula.DarculaUIUtil类中硬编码了发光效果的颜色，修改该类的两个成员，编译后替换同名文件：</p>
<pre class="crayon-plain-tag">public class DarculaUIUtil {
    public static final Color GLOW_COLOR = new JBColor(new Color(204, 86, 39), new Color(240, 119, 70));
    private static Color a() {
        return new JBColor(new Color(204, 86, 39), new Color(240, 119, 70));
    }
} </pre>
<p>以上步骤都完毕后，单选框还是顽固的蓝色，呵呵，又一个硬编码的颜色需要修改：</p>
<pre class="crayon-plain-tag">public class DarculaRadioButtonUI extends MetalRadioButtonUI {
    protected void paintIcon(JComponent var1, Graphics2D var2, Rectangle var3, Rectangle var4) {
        ...
        JBGradientPaint var11 = new JBGradientPaint( var1, new Color(240, 119, 70), new Color(204, 86, 39) );
    }
} </pre>
<p>最终效果的截图在<a href="#idea-screenshot">这里</a>，只是把IntelliJ主题的蓝色系改为橙色系，但是和Ubuntu系统很搭配呢。</p>
<div class="blog_h3"><span class="graybg">编辑器选区背景颜色</span></div>
<p>这个可以通过IDE设置：Settings ⇨ Editor ⇨ General，展开到Editor ⇨ Selection background，修改为F07746。该设置不但影响编辑器窗口，还会影响到Terminal、Messages、Event Log等基于文本的Tool window。</p>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">如何把当前的IDE布局保存为默认值</span></div>
<p>Window ⇨ Store Current Layout as Default</p>
<div class="blog_h3"><span class="graybg">Ubuntu下如何启用全局菜单栏（Global Menu）</span></div>
<p>安装jayatana：</p>
<pre class="crayon-plain-tag">sudo add-apt-repository ppa:danjaredg/jayatana
sudo apt-get update
sudo apt-get install jayatana</pre>
<p>修改IntelliJ的配置文件：</p>
<pre class="crayon-plain-tag"># 添加下面的内容
-javaagent:/usr/share/java/jayatanaag.jar</pre>
<p>此方法适用于任何Swing程序。</p>
<p>最新版本（至少2018）版本的Intellij，不再需要jayatana，而且jayatana可能导致Intellij功能异常。要启用全局菜单，搜索Action ，Experimental Features，勾选<pre class="crayon-plain-tag">linux.native.menu</pre></p>
<div class="blog_h3"><span class="graybg">改善Ubuntu下的字体渲染效果</span></div>
<p>安装修复过字体渲染的OpenJDK：</p>
<pre class="crayon-plain-tag">sudo add-apt-repository ppa:no1wantdthisname/openjdk-fontfix
sudo apt-get update
sudo apt-get install openjdk-7-jdk</pre>
<p>修改IntelliJ的配置文件：</p>
<pre class="crayon-plain-tag"># 添加下面的内容
-Dawt.useSystemAAFontSettings=lcd
-Dswing.aatext=true
-Dsun.java2d.xrender=true
-Dsun.java2d.pmoffscreen=false</pre>
<p>修改IntelliJ的启动脚本，使用刚下载的OpenJDK：</p>
<pre class="crayon-plain-tag">export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64</pre>
<p>更进一步，可以安装<a href="/ubuntu-faq#infinality">Infinality</a>，提升Ubuntu下字体渲染。</p>
<div class="blog_h3"><span class="graybg">界面乱码问题</span></div>
<p>Ubuntu14下的基于IntelliJ平台的IDE，有两个地方可以设置字体：</p>
<ol>
<li>A &amp; B ⇨ Appearance，Override Default fonts by 。这里可以设置IDE界面字体，包括菜单、Tool Window、Quick documentation弹窗等</li>
<li>Editor ⇨ Color &amp; Fonts ⇨ Font。这里可以设置编辑器字体</li>
</ol>
<p>默认情况下，系统语言设置为中文时，不进行任何设置就可以正常显示中文。</p>
<p>如果设置上面的第1项，需要保证选中的字体中包含中文字符的支持，否则很多地方的中文会显示为空心小方块。</p>
<p>最新的2016版本发布后，IntelliJ平台内置了修改后的Open JDK，提升了字体渲染效果，而且比进行过<a href="https://github.com/achaphiv/ppa-fonts/tree/master/openjdk-fontfix">Fontfix的Open JDK</a>字体更加清晰。试用后发现，系统语言设置为英文时，Quick documentation弹窗、搜索框中的中文都是乱码。除了把1设置为某种中文字体。</p>
<p>问题是，英文系统下默认字体“Ubuntu”比其它字体美太多，实在不想弃用。所以我把Wenquanyi Micro Hei中的中文合并到Ubuntu中，生成一个新字体“Ubuntu Micro Hei”，调整Hint后，发现英文部分显示效果和Ubuntu字体几乎无差，中文也支持了。下面是截图：<a id="idea-screenshot"></a><a href="https://blog.gmem.cc/wp-content/uploads/2015/10/Selection_005.png"><img class="aligncenter size-large wp-image-11773" src="https://blog.gmem.cc/wp-content/uploads/2015/10/Selection_005-1024x802.png" alt="Selection_005" width="100%" /></a>怎么样，是不是觉得比Mac OS X的渲染效果还惊艳<img src="https://blog.gmem.cc/wp-content/plugins/tinymce-emoticons/images/emoticons-set-1/giggle.png" alt="giggle" border="0" hspace="5" />（注：安装了<a href="/ubuntu-faq#infinality">Infinality</a>，并且渲染风格设置为OS X；IntelliJ中字体Antialiasing设置为Subpixel）</p>
<p>下面提供字体的下载链接：<a href="https://blog.gmem.cc/wp-content/uploads/2015/10/UbuntuMicroHei.ttf">Ubuntu Micro Hei</a>。</p>
<div class="blog_h3"><span class="graybg">如何读懂Local History</span></div>
<p>只需要记住：右侧面板显示的是，<span style="background-color: #c0c0c0;">当前时间点</span>相对左侧<span style="background-color: #c0c0c0;">选中的那个事件（时间点）发生<strong><span style="color: #ffffff; background-color: #ff0000;">之前</span></strong></span>，文件的差别：</p>
<ol>
<li>如果某个文件当前是有的，那个时间点是没有的，文件名称显示为绿色</li>
<li>如果某个文件在两个时间点都存在，但是有改动，文件名称显示为蓝色</li>
<li>如果某个文件在当前是没有的，而那个时间点是有的，文件名称显示为灰色</li>
</ol>
<p>示例截图：</p>
<p><img class="aligncenter size-full wp-image-12358" src="https://blog.gmem.cc/wp-content/uploads/2015/10/home-alex-JavaEE-projects-idea-pems-trunk-pems-pems-intelli-ctrl_001.png" alt="-home-alex-JavaEE-projects-idea-pems-trunk-pems-pems-intelli-ctrl_001" width="100%" /></p>
<p>在这个例子中。我们最后做了一条回退操作，在这之前，我们删除了两个文件，添加了一个文件。因而回退后，相对于回退前是添加了两个文件，删除了一个文件。 一定要明白“事件之前”而不是之后，如果是之后，那么Difference应该不存在。</p>
<div class="blog_h3"><span class="graybg">macOS下反复要求输入SVN密码</span></div>
<p>首先在钥匙串应用中删除对应的条目，然后通过IntelliJ 进行SVN操作，根据提示输入密码、点击记住。后续系统会提示是否允许IntelliJ访问钥匙串，选择总是允许。</p>
<div class="blog_h3"><span class="graybg">Ubuntu下无法使用Alt快捷键</span></div>
<p>在Ubuntu下，默认的你无法定制某些Alt + *的快捷键，原因是分配给Global菜单了。</p>
<p>要解决此问题，打开Settings ⇨ A &amp; B ⇨ Appearance，右侧找到Window Options区域，勾选 Disable mnemonics ...</p>
<div class="blog_h3"><span class="graybg">2017以后Ubuntu下中文输入闪烁</span></div>
<p>Inatellij 2017以后的版本，在Ubuntu下用IBUS输入中文，上屏时会出现闪烁。调试可以发现，它先渲染出一个英文字符大小的方框，然后在渲染出中文字符。</p>
<p>这个问题和零延迟渲染特性有关，设置系统属性<pre class="crayon-plain-tag">-Deditor.zero.latency.rendering=false</pre>即可解决。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/intellij-faq">IntelliJ平台知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/intellij-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CMake学习笔记</title>
		<link>https://blog.gmem.cc/cmake</link>
		<comments>https://blog.gmem.cc/cmake#comments</comments>
		<pubDate>Mon, 11 May 2015 08:09:21 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[IntelliJ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11488</guid>
		<description><![CDATA[<p>基础知识 CMake简介 CMake是一个开源的可扩展工具，用于独立于编译器的管理构建过程。CMake必须和本地构建系统联合使用，在每个源码目录中，需要编写CMakeLists.txt文件，以声明如何生成标准的构建文件（例如GNU Make的Makefiles，或者MSVS的解决方案）。 CMake支持所有平台的内部构建（in-source build）和外部构建（out-of-source build）。内部构建的源码目录和二进制目录为同一目录，即CMake会改变源码目录的内容。通过外部构建，可以针对单个源码树进行多重构建（Multiple builds ）。 CMake会生成一个方便用户编辑的缓存文件，当其运行时，会定位头文件、库、可执行文件，这些信息被收集到缓存文件中。用户可以在生成本地构建文件之前编辑它。 CMake命令行支持自动或者交互式的运行。CMake还提供了一个基于QT的GUI，其名称为cmake-gui。注意此GUI同样依赖于环境变量的正确设置。 基本语法 CMakeLists.txt包含一系列的命令，每个命令都是[crayon-69dc3b0caed03660852576-i/] 的形式，多个参数使用空白符分隔。CMake提供了很多预定义命令，你可以方便的扩展自己的命令。 CMake支持简单的变量，它们或者是字符串，或者是字符串的列表。引用一个变量的语法是[crayon-69dc3b0caed07630866568-i/] 。 如果向一个命令传递列表变量，效果等同于向它逐个传递列表成员： [crayon-69dc3b0caed09359394080/] 要把一个列表变量作为整体传递，只需要加上双引号即可：  [crayon-69dc3b0caed0b006986801/]  CMake可以直接访问环境变量和Windows注册表，前者使用语法[crayon-69dc3b0caed0d340153653-i/] ，后者使用语法[crayon-69dc3b0caed0f110751524-i/] 。示例： [crayon-69dc3b0caed11003626772/] CMake的优势 支持多个底层构建工具，例如GNU <a class="read-more" href="https://blog.gmem.cc/cmake">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/cmake">CMake学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">CMake简介</span></div>
<p>CMake是一个开源的可扩展工具，用于<span style="background-color: #c0c0c0;">独立于编译器</span>的管理构建过程。CMake<span style="background-color: #c0c0c0;">必须和本地构建系统联合</span>使用，在每个源码目录中，需要编写<span style="background-color: #c0c0c0;">CMakeLists.txt</span>文件，以声明如何生成标准的构建文件（例如GNU Make的Makefiles，或者MSVS的解决方案）。</p>
<p>CMake支持所有平台的内部构建（in-source build）和外部构建（out-of-source build）。内部构建的源码目录和二进制目录为同一目录，即CMake会改变源码目录的内容。通过外部构建，可以针对单个源码树进行<span style="background-color: #c0c0c0;">多重构建</span>（Multiple builds ）。</p>
<p>CMake会生成一个方便用户编辑的缓存文件，当其运行时，会定位头文件、库、可执行文件，这些信息被收集到缓存文件中。用户可以在生成本地构建文件之前编辑它。</p>
<p>CMake命令行支持自动或者交互式的运行。CMake还提供了一个基于QT的GUI，其名称为cmake-gui。注意此GUI同样依赖于环境变量的正确设置。</p>
<div class="blog_h3"><span class="graybg">基本语法</span></div>
<p>CMakeLists.txt包含一系列的命令，每个命令都是<pre class="crayon-plain-tag">COMMAND (args…)</pre> 的形式，多个参数使用空白符分隔。CMake提供了很多预定义命令，你可以方便的扩展自己的命令。</p>
<p>CMake支持简单的变量，它们或者是<span style="background-color: #c0c0c0;">字符串</span>，或者是<span style="background-color: #c0c0c0;">字符串的列表</span>。引用一个变量的语法是<pre class="crayon-plain-tag">${VAR_NAME}</pre> 。</p>
<p>如果向一个命令传递列表变量，效果等同于向它<span style="background-color: #c0c0c0;">逐个传递</span>列表成员：</p>
<pre class="crayon-plain-tag">set(V 1 2 3)    # V的值是1 2 3
command(${V})   # 等价于command(1 2 3)</pre>
<p>要把一个列表变量作为整体传递，只需要加上<span style="background-color: #c0c0c0;">双引号</span>即可： </p>
<pre class="crayon-plain-tag">command("${V}")   # 等价于command("1 2 3")</pre>
<p> CMake可以直接访问<span style="background-color: #c0c0c0;">环境变量</span>和<span style="background-color: #c0c0c0;">Windows注册表</span>，前者使用语法<pre class="crayon-plain-tag">$ENV{VAR}</pre> ，后者使用语法<pre class="crayon-plain-tag">[HKEY_CURRENT_USER\\SOFTWARE\\path;key]</pre> 。示例：</p>
<pre class="crayon-plain-tag"># 读取环境变量
message(STATUS "LD_LIBRARY_PATH=$ENV{LD_LIBRARY_PATH}" )
# 设置环境变量
set(ENV{PATH} "/home/alex/scripts")</pre>
<div class="blog_h3"><span class="graybg">CMake的优势</span></div>
<ol>
<li>支持多个底层构建工具，例如GNU Make、MSVC、XCode等等，可以生成这些构建工具需要的配置文件</li>
<li>通过分析环境变量、Windows注册表等，自动搜索构建所需的程序、库、头文件</li>
<li>支持创建复杂的命令</li>
<li>很方便的在共享库、静态库两种构建方式之间切换</li>
<li>自动<span style="background-color: #c0c0c0;">生成、维护C/C++文件</span>依赖关系，并且在大部分平台上支持<span style="background-color: #c0c0c0;">并行构建</span></li>
</ol>
<p>在开发跨平台软件时，CMake具有以下额外优势：</p>
<ol>
<li>可以测试机器字节序和其它硬件特性</li>
<li>统一的构建配置文件</li>
<li>支持依赖于机器特定信息的配置，例如文件的位置</li>
</ol>
<div class="blog_h2"><span class="graybg">安装CMake</span></div>
<pre class="crayon-plain-tag"># Ubuntu
sudo apt-get install cmake
# Redhat
yum install cmake
# Mac OS X with Macports
sudo port install cmake
# Window https://cmake.org/files/v3.5/cmake-3.5.2-win32-x86.zip</pre>
<div class="blog_h2"><span class="graybg">HelloWorld</span></div>
<p>C++源码：</p>
<pre class="crayon-plain-tag">#include &lt;iostream&gt;

using namespace std;

int main() {
    return 0;
}</pre>
<p>要通过CMake编译上述文件，需要在同一目录下放置CMakeLists.txt文件：</p>
<pre class="crayon-plain-tag"># 需要最小的CMake版本
cmake_minimum_required(VERSION 3.3)
# 工程的名称，会作为MSVS的Workspace的名字
project(intellij_taste)

# 全局变量：CMAKE_SOURCE_DIR CMake的起始目录，即源码的根目录
# 全局变量：PROJECT_NAME 工程的名称
# 全局变量：PROJECT_SOURCE_DIR 工程的源码根目录的完整路径

# 全局变量：构建输出目录。默认的，对于内部构建，此变量的值等于CMAKE_SOURCE_DIR；否则等于构建树的根目录
set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/bin)  # ${}语法用于引用变量
# 全局变量：可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# 全局变量：库文件的输出路径
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# 设置头文件位置
include_directories("${PROJECT_SOURCE_DIR}")
# 设置C++标志位
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# 设置源文件集合
set(SOURCE_FILES main.cpp)
# 添加需要构建的可执行文件，第二个以及后续参数是用于构建此文件的源码文件
add_executable(intellij_taste ${SOURCE_FILES})</pre>
<p>在上述目录中执行下面两条命令，即可执行构建：</p>
<pre class="crayon-plain-tag"># 创建一个build子目录作为构建树
mkdir build &amp;&amp; cd build &amp;&amp; cmake .. &amp;&amp; cd ..

# 在build/bin子目录中生成可执行文件：
# cmake --build &lt;dir&gt; [options] [-- [native-options]]
cmake --build build -- -j3  # --表示把其余选项传递给底层构建工具

# 注意，亦可使用底层构建系统，例如make命令或者MSVC的IDE
cd build
make -j3</pre>
<div class="blog_h1"><span class="graybg">核心理念</span></div>
<p>CMake包含一系列重要的概念抽象，包括目标（Targets）、生成器（Generators）、命令（Commands）等，这些命令均被实现为C++类。理解这些概念后才能编写高效的CMakeLists文件。</p>
<p>下面列出这些概念之间的基本关系：</p>
<ol>
<li>源文件：对应了典型的C/C++源代码</li>
<li>目标：多个源文件联合成为目标，<span style="background-color: #c0c0c0;">目标通常是可执行文件或者库</span></li>
<li>目录：表示源码树中的一个目录，常常包含一个CMakeLists.txt文件，一或多个目标与之关联</li>
<li>本地生成器（Local generator）：每个目录有一个本地生成器，负责为此目录生成Makefiles，或者工程文件</li>
<li>全局生成器（Global generator）：所有本地生成器共享一个全局生成器，后者负责监管构建过程，全局生成器由CMake本身创建并驱动</li>
</ol>
<p>CMake的执行开始时，会创建一个cmake对象并把命令行参数传递给它。cmake对象管理整体的配置过程，持有构建过程的全局信息（例如缓存值）。cmake会依据用户的选择来创建合适的全局生成器（VS、Makefiles等等），并把构建过程的控制权转交给全局生成器（调用configure和generate方法）。</p>
<p>全局生成器负责管理配置信息，并生成所有Makefiles/工程文件。一般情况下全局生成器把具体工作委托给本地生成器执行，全局生成器为每个目录创建一个本地生成器。全局/本地生成器的分工取决于实现，例如：</p>
<ol>
<li>对于VS，全局生成器负责生成解决方案文件，本地生成器负责每个目标的工程文件</li>
<li>对于Makefiles，全局生成器生成总体的Makefile，本地生成器则负责生成大部分Makefile</li>
</ol>
<p>每个本地生成器包含一个cmMakefile对象，其中存放CMakeList.txt的解析结果。</p>
<p>CMake的每一个命令也被实现为C++类，该类主要包括两个成员：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">成员</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>InitialPass()</td>
<td>接受当前目录的cmMakefile对象、命令参数作为入参。命令的执行结果存放在cmMakefile对象中</td>
</tr>
<tr>
<td>LastPass()</td>
<td>在整个CMake工程所有命令的InitialPass()都执行后再执行。大部分命令不实现此方法</td>
</tr>
</tbody>
</table>
<p>下图显示cmake、生成器、cmMakefile、命令等类型的关系：<img class="aligncenter size-full wp-image-11546" src="https://blog.gmem.cc/wp-content/uploads/2015/05/cmake-class-diagram.png" alt="cmake-class-diagram" width="636" height="804" /></p>
<div class="blog_h2"><span class="graybg">目标</span></div>
<p>cmMakefile对象中存放的最重要的对象是目标（Targets），目标代表可执行文件、库、实用工具等。每个<pre class="crayon-plain-tag">add_library</pre> 、<pre class="crayon-plain-tag">add_executable</pre> 、<pre class="crayon-plain-tag">add_custom_target</pre> 命令都会创建一个目标。</p>
<div class="blog_h3"><span class="graybg">库目标</span></div>
<p>下面的语句创建一个库目标：</p>
<pre class="crayon-plain-tag"># 创建一个静态库，包含两个源文件
add_library(foo STATIC foo1.c foo2.c)</pre>
<p>上述命令声明的foo可以作为库名称在工程的任何地方使用。CMake知道如何将此名称转换为库文件。命令的（可选的）第二个参数声明库的类型，有效值包括： </p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">库类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>STATIC</td>
<td>目标必须构建为静态库</td>
</tr>
<tr>
<td>SHARED</td>
<td>目标必须构建为共享库</td>
</tr>
<tr>
<td>MODULE</td>
<td>目标必须构建为支持在运行时动态加载到可执行文件中的模块<br />对于除了Mac OS X之外的系统，此取值等价于SHARED</td>
</tr>
</tbody>
</table>
<p>如果不声明库类型，则CMake依据变量<pre class="crayon-plain-tag">BUILD_SHARED_LIBS</pre> 判断应该构建为共享库还是静态库，如果此变量不设置，构建为静态库。 </p>
<div class="blog_h3"><span class="graybg">可执行目标</span></div>
<p>与库目标类似，可执行目标也可以指定特定的选项，例如WIN32会导致操作系统调用WinMain而不是main函数。</p>
<div class="blog_h3"><span class="graybg">读写目标属性</span></div>
<p>使用<pre class="crayon-plain-tag">set_target_properties</pre> 或者<pre class="crayon-plain-tag">get_target_properties</pre> 命令，或者更通用的<pre class="crayon-plain-tag">set_property</pre> 、<pre class="crayon-plain-tag">get_property</pre> 命令，可以读写目标的属性，示例：</p>
<pre class="crayon-plain-tag"># 修改目录使用的头文件目录，注意，全局的include_directories会此目标忽略
set_property (TARGET jsonrpc
  PROPERTY INCLUDE_DIRECTORIES
    ${JSONCPP_INCLUDE_DIRS}
    ${Boost_INCLUDE_DIRS}
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/jsonrpc
)

# 同时设置多个属性
set_target_properties(jsonrpc PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR})</pre>
<p>常用目标属性如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>LINK_FLAGS</td>
<td>指定（传递给）链接（器的）标记。示例：<br />
<pre class="crayon-plain-tag">set_target_properties(
    cstudy PROPERTIES
    INCLUDE_DIRECTORIES /home/alex/.local/include
    # 使用定制的glibc库
    LINK_FLAGS "-Wl,-rpath=/home/alex/.local/lib  -Wl,--dynamic-linker=/home/alex/.local/lib/ld-linux-x86-64.so.2"
)</pre>
</td>
</tr>
<tr>
<td>COMPILE_FLAGS</td>
<td>指定（传递给）编译（器的）标记</td>
</tr>
<tr>
<td>INCLUDE_DIRECTORIES</td>
<td>指定目标需要引用的头文件目录</td>
</tr>
<tr>
<td>PUBLIC_HEADER</td>
<td>共享的库目标提供的公共头文件</td>
</tr>
<tr>
<td>VERSION<br />SOVERSION</td>
<td>
<p>对于共享库来说，VERSION、SOVERSION允许让你分别设置构建版本、API版本。通常SOVERSION更加稳定不变</p>
<p>如果设置了NO_SONAME属性，则SOVERSION属性被自动忽略</p>
</td>
</tr>
<tr>
<td>OUTPUT_NAME</td>
<td>
<p>目标输出文件名称</p>
</td>
</tr>
</tbody>
</table>
<p>全部目标属性请<a href="https://cmake.org/cmake/help/v3.0/manual/cmake-properties.7.html#properties-on-targets">参考官网</a>。</p>
<div class="blog_h3"><span class="graybg">链接到库</span></div>
<p>使用<pre class="crayon-plain-tag">target_link_libraries</pre> 命令，可以指定目标需要链接的库的列表。列表的元素可以是库、库的全路径、通过add_library命令添加的库名称。</p>
<p>对于声明的每个库，CMake会跟踪其依赖的所有其它库，这种依赖关系需要用上述命令来设置：</p>
<pre class="crayon-plain-tag">add_library(foo foo.cpp)
#foo库依赖于bar库
target_link_libraries(foo bar)

add_executable(foobar foobar.cpp)
#foobar显式依赖foo，隐式依赖bar，后两者都会被链接到foobar中
target_link_libraries(foobar foo)</pre>
<div class="blog_h2"><span class="graybg">源文件</span></div>
<p>和Target类似，源文件也被建模为C++类，也支持读写属性（通过set_source_files_properties、get_source_files_properties或更加一般的命令）。最常用属性包括：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>COMPILE_FLAGS</td>
<td>针对特定源文件的编译器标记，可以包含-D、-I之类的标记</td>
</tr>
<tr>
<td>GENERATED</td>
<td>指示此文件是否在构建过程中生成，这种文件在CMake首次运行时不存在，因而计算依赖关系时要特殊考虑</td>
</tr>
<tr>
<td>OBJECT_DEPENDS</td>
<td>添加此源文件额外依赖的其它文件。CMake会自动分析C、C++的源文件依赖，因而此选项很少使用</td>
</tr>
<tr>
<td>WRAP_EXCLUDE</td>
<td>CMake不直接使用该属性。但是某些命令和扩展读取该属性，判断何时/如何把C++类包装到其它语言，例如Python</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">目录、生成器、测试、属性</span></div>
<p>其它偶尔可能用到的CMake类型包括Directory、Generator、Test、Property等。Directory、Generator、Test的实例同样（与目录、源文件类似）关联属性。</p>
<p>属性是一种键值存储，它关联到一个对象。读写属性最一般的方法是上面提到的get/set_property命令。所有可用的属性可以通过<pre class="crayon-plain-tag">cmake -help-property-list</pre> 得到。</p>
<p>目录的属性包括：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ADDITIONAL_MAKE_CLEAN_FILES</td>
<td>指定一系列需要在make clean时清除掉的文件的列表<br />默认的CMake会清除所有生成的文件</td>
</tr>
<tr>
<td>EXCLUDE_FROM_ALL</td>
<td>指示此目录和子目录中所有的目标，是否应当从默认构建中排除<br />子目录的IDE工程文件/Makefile将从顶级IDE工程文件/Makefile中排除</td>
</tr>
<tr>
<td>LISTFILE_STACK</td>
<td>最要在调试CMake脚本时用到，列出当前正在被处理的文件的列表</td>
</tr>
</tbody>
</table>
<p>目录和生成器对象会在CMake处理你的源码树时自动创建。</p>
<div class="blog_h2"><span class="graybg">变量和缓存条目（Cache Entries）</span></div>
<div class="blog_h3"><span class="graybg">变量</span></div>
<p>CMakeLists中的变量和普通编程语言中的变量很类似，变量的值要么是单个值，要么是列表。CMake自动定义一系列重要的变量。</p>
<p>要引用变量，必须使用<pre class="crayon-plain-tag">${VARNAME}</pre> 语法，要设置变量的值，需要使用set命令。</p>
<p>CMake中变量的作用域和普通编程语言略有不同，当你设置一个变量后，变量对当前CMakeLists文件、当前函数、<span style="background-color: #c0c0c0;">以及子目录的CMakeLists</span>、任何通过<pre class="crayon-plain-tag">INCLUDE</pre> <span style="background-color: #c0c0c0;">包含进来的文件</span>、任何<span style="background-color: #c0c0c0;">调用的宏或函数</span>可见。</p>
<p>当处理一个子目录、调用一个函数时，CMake创建一个新的作用域，其复制当前作用域全部变量，在<span style="background-color: #c0c0c0;">子作用域中对变量的修改不会对父作用域产生影响</span>。要修改父作用域中的变量，可以在set时指定特殊选项：</p>
<pre class="crayon-plain-tag">set (name Alex PARENT_SCOPE)</pre>
<p>变量的值可以是一个列表，这样的变量可以被展开为多个值：</p>
<pre class="crayon-plain-tag">set(fruit apple peach strawberry )
foreach(f ${fruit})
    message("Do you want ${f}")
endforeach()</pre>
<p><a id="useful-vars"></a>常用变量如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>CMAKE_C_FLAGS</td>
<td>C编译标记，示例：<pre class="crayon-plain-tag">set(CMAKE_C_FLAGS "-std=c11 -pthread")</pre></td>
</tr>
<tr>
<td>CMAKE_CXX_FLAGS</td>
<td>C++编译标记</td>
</tr>
<tr>
<td>CMAKE_C_FLAGS_DEBUG</td>
<td>用于Debug配置的C编译标记，示例：<pre class="crayon-plain-tag">set(CMAKE_C_FLAGS_DEBUG "-g -O0")</pre></td>
</tr>
<tr>
<td>CMAKE_CXX_FLAGS_DEBUG</td>
<td>用于Debug配置的C++编译标记</td>
</tr>
<tr>
<td>CMAKE_C_FLAGS_RELEASE</td>
<td>用于Release配置的C编译标记</td>
</tr>
<tr>
<td>CMAKE_CXX_FLAGS_RELEASE</td>
<td>用于Release配置的C++编译标记</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">缓存条目</span></div>
<p>有些时候你可能期望<span style="background-color: #c0c0c0;">用户</span>通过CMake的UI<span style="background-color: #c0c0c0;">输入</span>一些变量的值， 这时<span style="background-color: #c0c0c0;">变量必须作为缓存条目</span>。当CMake运行时，它会向二进制目录输出缓存文件（Cache file），缓存文件中的变量值通过CMake的UI展示给用户。</p>
<p>使用这种缓存的<span style="background-color: #c0c0c0;">目的之一</span>是，存储用户的选项，避免重新运行CMake时，反复要求用户输入相同的信息。</p>
<p><pre class="crayon-plain-tag">option</pre> 命令可以创建一个Boolean变量（ON/OFF）并将其存储在缓存中：</p>
<pre class="crayon-plain-tag">option(USE_PNG "Do you want to use the png library?")</pre>
<p>用户可以通过UI设置USE_PNG的值，并且在未来这一值会保存在缓存中。使用CLion作为IDE时，可以在CMake窗口中点击Cache选项卡，查看或者编辑缓存条目：</p>
<p><img class="aligncenter wp-image-11640 size-full" src="https://blog.gmem.cc/wp-content/uploads/2015/05/Selection_002.png" alt="Selection_002" width="710" /></p>
<p>除了<pre class="crayon-plain-tag">option</pre> 命令之外，<pre class="crayon-plain-tag">find_file</pre> 也可以用来创建缓存条目。为<pre class="crayon-plain-tag">set</pre> 命令指定特殊参数，亦可创建缓存条目： </p>
<pre class="crayon-plain-tag"># CACHE选项表示此变量作为缓存条目
# ON为默认值
# BOOL为变量类型，支持BOOL、PATH、FILEPATH、STRING
set(USE_PNG ON CACHE BOOL "Do you want to use the png library?")</pre>
<p>缓存条目的<span style="background-color: #c0c0c0;">另外一个目的</span>是， 存储那些难以确定的关键变量，这些变量可能对用户不可见。通常这些变量是系统相关的，例如<pre class="crayon-plain-tag">CMAKE_WORDS_BIGENDIAN</pre> 。这类值可能需要CMake编译并运行一个程序来确定，一旦确定，即缓存。</p>
<p>位于缓存中的变量具有一个属性指示它是否为“进阶的”（advanced），默认的CMake GUI隐藏进阶条目。要标记一个缓存条目为进阶的，可以：</p>
<pre class="crayon-plain-tag">mark_as_advanced(VAR_NAME)</pre>
<p>某些情况下，你可能需要限制缓存条目的值范围在一个有限的集合中，这是可以设置条目的<pre class="crayon-plain-tag">STRINGS</pre> 属性，提供值列表。在GUI中，这种条目的字段会展示为下拉列表：</p>
<pre class="crayon-plain-tag"># 设置名为CRYPT_BACKEND的缓存条目的值为Open SSL
set(CRYPT_BACKEND "Open SSL" CACHE STRING)
# 设置上述缓存条目的取值范围
set_property(CACHE CRYPT_BACKEND PROPERTY STRINGS "Open SSL" "LibDES")</pre>
<p>即使变量存在于缓存，你仍然可以在CMakeLists中<span style="background-color: #c0c0c0;">覆盖它（改变作用域中此变量的值）</span>。只需要不带CACHE选项调用set命令，即可覆盖缓存中同名变量的值。 </p>
<p>另一方面，一旦变量值已经缓存，你一般无法在CMakeLists中改变缓存的值（与上述覆盖是两回事）。也就是说，当缓存中有VARNAME时，<pre class="crayon-plain-tag">set(VARNAME ON CACHE BOOL )</pre> 不会有任何作用。要<span style="background-color: #c0c0c0;">强制改变缓存中的值并覆盖</span>当前作用域的值，可以联合使用<pre class="crayon-plain-tag">FORCE</pre> 和<pre class="crayon-plain-tag">CACHE</pre>选项。</p>
<div class="blog_h2"><span class="graybg">构建配置</span></div>
<p>构建配置允许工程使用不同方式构建：debug、optimized或者任何其它标记。CMake默认支持四种构建配置：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">构建配置</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Debug</td>
<td>启用基本的调试（编译器的）标记</td>
</tr>
<tr>
<td>Release</td>
<td>基本的优化配置</td>
</tr>
<tr>
<td>MinSizeRel</td>
<td>生成最小化的，但不一定是最快的代码</td>
</tr>
<tr>
<td>RelWithDebugInfo</td>
<td>优化构建，但是同时携带调试信息</td>
</tr>
</tbody>
</table>
<p>依据生成器的不同，CMake处理构建配置的方式有所差异，CMake尽可能遵循底层本地构建系统的约定，这意味着使用Makefiles、VS时构建配置影响构建的方式有所不同：</p>
<ol>
<li>VS支持构建配置的概念，在IDE中你可以选择Debug、Release配置，CMake只需要对接到VS的构建配置即可</li>
<li>Makefile默认同时（CMake运行时）只能有一种配置被激活。使用<pre class="crayon-plain-tag">CMAKE_BUILD_TYPE</pre> 变量可以指定目标配置。如果此变量为空，则不给构建添加额外标记。如果此变量设置为上面四种构建配置之一，则相应的变量、规则——例如<pre class="crayon-plain-tag">CMAKE_CXX_FLAGS_&lt;CONFIGNAME&gt;</pre> 被添加到compile line中。可以使用下面的方式来分别基于Debug、Release配置进行构建：<br />
<pre class="crayon-plain-tag"># 创建工程目录的两个兄弟目录，CD到其中分别执行：
cmake ../project -DCMAKE_BUILD_TYPE:STRING=Debug
cmake ../project -DCMAKE_BUILD_TYPE:STRING=Release </pre>
</li>
</ol>
<div class="blog_h1"><span class="graybg">编写CMakeLists文件</span></div>
<p>CMake由CMakeLists.txt驱动，此文件包含构建需要的一切信息。</p>
<p>除了用于分隔命令参数，其余空白符一律被忽略。反斜杠可以用来指示转义字符。</p>
<div class="blog_h2"><span class="graybg">基本命令</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>project</td>
<td>顶层CMakeLists.txt中应当包含的第一个命令，声明工程的名字和使用的编程语言：<br />
<pre class="crayon-plain-tag">project (projectname, [CXX], [C], [JAVA], [NONE])</pre></p>
<p>如果不指定语言，默认CMake启用C/C++，如果指定为CXX则C语言的支持自动加入</p>
<p>对于工程中出现的<span style="background-color: #c0c0c0;">每个</span>project命令，CMake会创建一个<span style="background-color: #c0c0c0;">顶级的IDE工程文件</span>（或Makefile文件） 。此工程文件中会包含：</p>
<ol>
<li>所有CMakeLists.txt中声明的目标</li>
<li>所有通过<pre class="crayon-plain-tag">add_subdirectory</pre> 命令添加的子目录。如果为命令指定<pre class="crayon-plain-tag">EXCLUDE_FROM_ALL</pre> 选项，则此工程文件/Makefile不会包含到顶级工程文件/Makefile中，对于那种需要从主构建流传中排除的子工程（例如examples子工程），这个选项有用</li>
</ol>
</td>
</tr>
<tr>
<td>set</td>
<td>设置变量值或列表</td>
</tr>
<tr>
<td>remove</td>
<td>从变量值的列表中移除一个单值</td>
</tr>
<tr>
<td>separate_arguments</td>
<td>基于空格，把单个字符串分隔为列表</td>
</tr>
<tr>
<td>add_executable</td>
<td rowspan="2">定义目标（可执行文件/库），以及目录由哪些源文件组成<br />对于VS，源文件将会出现在IDE中，但是默认的项目使用的头文件不会包含在IDE中，要改变此行为，只需要将头文件添加到源文件列表中</td>
</tr>
<tr>
<td>add_library</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">流程控制命令</span></div>
<p>和普通编程语言一样，CMake支持条件、循环控制结构，同时支持子过程（macro、function）</p>
<div class="blog_h3"><span class="graybg">if-else-endif</span></div>
<pre class="crayon-plain-tag">if (FOO)
else(FOO)
endif(FOO)
# 上面把if的条件在else、endif中重复，这是可选的。因此我们可以简单的写作：
if (FOO)
else()
endif()</pre>
<p>在else、endif上重复条件，有助于if-else-endif匹配检查，特别是多层嵌套时。</p>
<div class="blog_h3"><span class="graybg">elseif</span></div>
<p>CMake同样支持elseif：</p>
<pre class="crayon-plain-tag">if(MSVC80)
  #...
elseif(MSVC90)
  #...
elseif(APPLE)
  #...
endif()</pre>
<div class="blog_h3"><span class="graybg">条件表达式</span></div>
<p>条件命令支持受限的表达式语法，如下表所列：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">语法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>if ( variable )</td>
<td>
<p>当if命令参数的值不是：<span style="background-color: #c0c0c0;">0、FALSE、OFF、NO、NOTFOUND、*-NOTFOUND、IGNORE</span>时，表达式的值为真，注意<span style="background-color: #c0c0c0;">不区分大小写<br /></span>variable可以不用${}包围</p>
</td>
</tr>
<tr>
<td>if ( NOT variable )</td>
<td>上面取反<br />variable可以不用${}包围</td>
</tr>
<tr>
<td>if ( variable1 AND variable2 )</td>
<td>逻辑与，所有逻辑操作支持<span style="background-color: #c0c0c0;">用括号来提升优先级</span></td>
</tr>
<tr>
<td>if ( variable1 OR variable2 )</td>
<td>逻辑或</td>
</tr>
<tr>
<td>if ( num1 EQUAL num2 )</td>
<td>数字相等比较，其它操作符包括LESS、GREATER</td>
</tr>
<tr>
<td>if ( str1 STREQUAL str2 )</td>
<td>字典序相等比较，其它操作符包括STRLESS、STRGREATER</td>
</tr>
<tr>
<td>if ( v1 VERSION_EQUAL v2)</td>
<td><pre class="crayon-plain-tag">marjor[.minor[.patch[.tweak]]]</pre> 风格的版本号相等比较，其它操作符包括VERSION_LESS、VERSION_GREATER</td>
</tr>
<tr>
<td>if ( COMMAND  commandname )</td>
<td>如果指定的命令可以调用</td>
</tr>
<tr>
<td>if ( DEFINED variable )</td>
<td>如果指定的变量被定义，不管它的值真假</td>
</tr>
<tr>
<td>if ( EXISTS file-name )</td>
<td>如果指定的文件或者目录存在</td>
</tr>
<tr>
<td>if ( IS_DIRECTORY name )</td>
<td>如果给定的name是一个目录</td>
</tr>
<tr>
<td>if ( IS_ABSOLUTE name )</td>
<td>如果给定的name是一个绝对路径</td>
</tr>
<tr>
<td>if ( n1 IS_NEWER_TAN n2 )</td>
<td>如果文件n1的修改时间大于n2</td>
</tr>
<tr>
<td>if ( variable MATCHES regex )</td>
<td rowspan="2">如果给定的变量或者字符串匹配正则式：<br />
<pre class="crayon-plain-tag">set(name Alex)
if(${name} MATCHES A.*x)
    message(${name})
endif()</pre>
</td>
</tr>
<tr>
<td>if ( string MATCHES regex )</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">操作符优先级</span></div>
<p>CMake操作符优先级从高到底：</p>
<ol>
<li>括号分组：()</li>
<li>前缀一元操作符：EXISTS、COMMAND、DEFINED</li>
<li>比较操作符：EQUAL、LESS、GREATER及其变体，以及MATCHES</li>
<li>逻辑非：NOT</li>
<li>逻辑或于：AND、OR</li>
</ol>
<div class="blog_h3"><span class="graybg">foreach</span></div>
<pre class="crayon-plain-tag">foreach (item  list)
    # do something with item
endforeach (item)</pre>
<p>此命令用于迭代一个列表，第一个参数是每次迭代使用变量的名称，其余参数为被迭代的列表</p>
<p>注意，在循环内部，你可以使用迭代变量<span style="background-color: #c0c0c0;">构造另外一个变量的名字</span>，例如<pre class="crayon-plain-tag">${NAME_OF_${item}}</pre> </p>
<div class="blog_h3"><span class="graybg">while</span></div>
<p>此命令用于<span style="background-color: #c0c0c0;">基于条件的</span>迭代：</p>
<pre class="crayon-plain-tag">while(${COUNT} LESS 2000)
    set(TASK_COUNT, ${COUNT})
endwhile()</pre>
<div class="blog_h3"><span class="graybg">break</span></div>
<p>此命令用于中断foreach/while循环。</p>
<div class="blog_h3"><span class="graybg">function</span></div>
<p>CMake中的函数很类似于C/C++函数。你可以向函数传递参数，除了依据形参名外，你还可以使用<pre class="crayon-plain-tag">ARGC</pre> 、<pre class="crayon-plain-tag">ARGV</pre> 、<pre class="crayon-plain-tag">ARGN</pre> 、<pre class="crayon-plain-tag">ARG0</pre> 、<pre class="crayon-plain-tag">ARG1</pre> ...等形式，在函数内部访问入参。</p>
<p>函数内部是一个新作用域，类似于add_subdirectory生成的新作用域一样，函数<span style="background-color: #c0c0c0;">调用前的作用域被拷贝并传递到函数内部</span>，函数返回时，新作用域消失。</p>
<p>函数的<span style="background-color: #c0c0c0;">第一个形参是函数的名称</span>，其它参数构成传统的<span style="background-color: #c0c0c0;">形参列表</span>：</p>
<pre class="crayon-plain-tag">function(println msg)
    message(${msg} "\n")
    set ( msg ${msg} PARENT_SCOPE )  #设置父作用域中变量的值
endfunction()

println(Hello)</pre>
<div class="blog_h3"><span class="graybg">return</span></div>
<p>此命令拥有从函数中返回，或者在listfile命令中提前结束。</p>
<div class="blog_h3"><span class="graybg">macro</span></div>
<p>宏于函数类似，但是<span style="background-color: #c0c0c0;">宏不会创建新的</span>作用域。传递给宏的参数也不被作为变量看待，而是在<span style="background-color: #c0c0c0;">执行宏前替换为字符串</span>：</p>
<pre class="crayon-plain-tag">macro (println msg)      #同样的，括号中第一个项目是宏的名称
    message(${msg} "\n")
endmacro()</pre>
<p>对于宏， ARGC、ARG0、ARG1等也可以使用。ARG0代表传递给宏的第一个参数。</p>
<div class="blog_h2"><span class="graybg">检查CMake的版本</span></div>
<p>CMake是一个不断进化的工具，随着新版本的推出，会不断有新的命令被加入。很多时候，我们需要检查当前CMake版本是否支持某些特性。</p>
<p>我们可以使用if命令判断某个命令是否可用：</p>
<pre class="crayon-plain-tag">if(COMMAND some_new_command)
    #...
endif()</pre>
<p>或者直接检查CMake的版本：</p>
<pre class="crayon-plain-tag">if (${CMAKE_VERSION} VERSION_GREATER 1.6.1)
endif()</pre>
<p>另外，还可以声明要求的最低的CMake版本：</p>
<pre class="crayon-plain-tag">cmake_minimum_required(VERSION 2.8)</pre>
<div class="blog_h2"><span class="graybg">使用模块</span></div>
<p>所谓模块，仅仅是存放到一个文件中，一系列CMake命令的集合。我们可以用<pre class="crayon-plain-tag">include</pre> 命令将模块包含到CMakeLists.txt中。举例：</p>
<pre class="crayon-plain-tag"># 此模块用于查找TCL库
include (FindTCL)
# 找到后，将其加入到链接依赖中
target_link_libraries (FOO ${TCL_LIBRARY})</pre>
<p>包含一个模块时，可以使用绝对路径，或者是基于CMAKE_MODULE_PATH的相对路径，如果此变量未设置，默认为CMake的安装目录的Modules子目录。</p>
<p>模块依据用途的不同可以分为：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">类别</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>查找类模块</td>
<td>
<p>查找软件元素——例如头文件、库——的位置</p>
<p>CMake提供了大量这类模块，如果目录库/头文件找不到，模块往往提供一个缓存条目，便于用户手工指定</p>
<p>下面是一个查找PNG模块的例子：</p>
<pre class="crayon-plain-tag"># png库依赖于zlib
include(FindZLIB)  # 查找zlib库
if (ZLIB_FOUND)    # 往往在找到后设置LIBNAME_FOUND变量
    # 查找头文件位置并存入变量
    find_path(PNG_PNG_INCLUDE_DIR png.h /usr/local/include /usr/include)
    # 查找库文件位置并存入变量    
    find_library(PNG_LIBRARY png /usr/lib /usr/local/lib)
    if (PNG_LIBRARY AND PNG_PNG_INCLUDE_DIR)
        # 合并ZLIB头文件和库到PNG的
        set(PNG_INCLUDE_DIR ${PNG_PNG_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
        set(PNG_LIBRARIES ${PNG_LIBRARY} ${ZLIB_LIBRARY})
        # 设置已找到标记
        set(PNG_FOUND YES)
    endif ()
endif ()</pre>
</td>
</tr>
<tr>
<td>系统探测模块</td>
<td>
<p>探测系统的特性，例如浮点数长度、对ASCI C++刘的支持
<p>很多这类模块具有Test、Check前缀，例如TestBigEndian、CheckTypeSize</p>
</td>
</tr>
<tr>
<td>实用工具模块</td>
<td>用于添加额外的功能，例如处理一个CMake工程依赖于其它CMake工程的情况</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">策略（Policies）</span></div>
<p>由于某些原因，在版本升级后，CMake可能不提供完全的向后兼容。这意味着使用新版的CMake处理基于旧版本的CMakeLists.txt时会出现问题。CMake引入策略这一特性，帮助用户和开发者处理此向后兼容问题。</p>
<p>策略机制实现以下目标：</p>
<ol>
<li>既有的工程能够用<span style="background-color: #c0c0c0;">任何</span>比CMakeLists作者使用的、更新版本的CMake构建。用户不应该需要修改CMakeLists代码，但是可能出现警告信息</li>
<li> 新特性的修正，老接口的Bug修复应当被执行，而非因向后兼容性的要求而搁置</li>
<li>任何对CMake的改变，会导致CMakeLists文件必须更改的，应当加以文档说明。每个这样的改变应当具有唯一的标识符以便查阅文档，改变仅<span style="background-color: #c0c0c0;">在工程提示自己支持的情况下</span>才启用</li>
<li>最终将会移除向后兼容性的代码，<span style="background-color: #c0c0c0;">不再支持古老版本</span>的CMake。因此而构建失败的工程必须得到有价值的错误提示</li>
</ol>
<p>CMake中的所有策略被分配一个<pre class="crayon-plain-tag">CMPNNNN</pre> 形式的名称，其中NNNN是一个整数值编号。策略同时支持<span style="background-color: #c0c0c0;">出于兼容性目的的旧行为，以及“正确的”新行为</span>。每个策略包含出现动机、新旧行为的详细说明文档。</p>
<div class="blog_h3"><span class="graybg">设置策略</span></div>
<p>可以在工程中对每个策略进行配置，设置其值为NEW或者OLD，CMake将遵从测量设置，从而表现出不同的构建行为。</p>
<p>设置策略有几种方式，最简单的是设置策略为特定的CMake版本：<pre class="crayon-plain-tag">cmake_policy(VERSION 2.6)</pre> 。这样所有2.6版本之前引入的策略都被标记为NEW，而2.6之后引入的策略则标记为“未设置”，以便产生警告信息。</p>
<p>注：cmake_minimum_required命令同样会设置策略，因此仅在需要定制子目录的策略时才以VERSION选项调用cmake_policy命令。</p>
<p>以SET选项调用cmake_policy可以明确的设置单个策略。以CMP0002为例，该策略的新行为要求所有逻辑目标具有全局独特的名字。下面的命令可以抑制存在重复目标名时的警告信息：</p>
<pre class="crayon-plain-tag">cmake_policy(SET CMP0002 OLD)</pre>
<div class="blog_h2"><span class="graybg">链接到库</span></div>
<pre class="crayon-plain-tag"># 设置库的寻找目录
link_directories(/path/to)
add_executable(myexe myexe.c)
target_link_libraries (myexe A B)

# 或者
add_executable(myexe myexe.c)
# 使用绝对路径
target_link_libraries (myexe /path/to/libA.so /path/to/libB.so )</pre>
<div class="blog_h3"><span class="graybg">链接到系统库</span></div>
<p>类Unix操作系统的系统库常常位于/usr/lib或者/lib目录。这些目录被链接器作为隐含的库搜索目录，因此<pre class="crayon-plain-tag">find_library(M_LIB m)</pre> 将从/usr/lib/libm.so定位到Math库。</p>
<p>问题是，某些平台会依据体系结构的不同，提供库的不同版本：</p>
<pre class="crayon-plain-tag"># IRIX机器
/usr/lib/libm.so         # ELF o32
/usr/lib32/libm.so       # ELF n32
/usr/lib64/libm.so       # ELF 64
# Solaris
/usr/lib/lim.so          # sparcv8架构
/usr/lib/sparcv9/lim.so  # sparcv9架构</pre>
<p>find_library命令不知道各种体系结构特定的系统如何定义上面的目录规则，因此此命令可能找到<span style="background-color: #c0c0c0;">不匹配的体系结构的</span>库文件。</p>
<p>此问题的一个解决办法是让<span style="background-color: #c0c0c0;">链接器自动寻找</span>库所在目录（不使用link_directories或者指定绝对路径），不幸的是，此办法无法区分库的动态、静态版本。CMake实际使用的妥协做法是：</p>
<ol>
<li><span style="background-color: #c0c0c0;">存在于隐含库搜索目录</span>中的库，且链接器<span style="background-color: #c0c0c0;">支持类似-Bstatic</span>的选项来指定使用静态库，使用-l选项传递库名称</li>
<li>其它情况下，传递库绝对路径给链接器</li>
</ol>
<div class="blog_h2"><span class="graybg">共享库和可加载模块</span></div>
<p>共享库和可加载模块有利于重用：</p>
<ol>
<li>缩短<span style="background-color: #c0c0c0;">compile/link/run周期</span></li>
<li>共享库重新构建时，依赖于它的共享库/可执行文件甚至<span style="background-color: #c0c0c0;">不需要重新构建</span></li>
<li>减少磁盘和内存消耗，因为同一共享库只需要一份</li>
</ol>
<p>相比静态库，共享库更像是可执行文件，大部分系统要求共享库上具有可执行权限。和可执行文件一样，共享库可以链接到其它共享库。</p>
<p>对于静态库来说，一个object文件是最小单元；而共享库（包括其依赖）本身是一个最小单元。链接器可以从静态库中挑出需要的object文件，但是对于共享库及其依赖的其它共享库，都需要存在。</p>
<p>共享库和静态库的另外一个不同是库的声明顺序，指定静态库时顺序很重要，因为大部分链接器仅仅遍历库列表一次来寻找符号，依赖其它静态库的静态库必须放在列表前面。</p>
<p>当决定在工程使用共享库时，开发者必须面对几个问题。</p>
<div class="blog_h3"><span class="graybg">共享库导出哪些符号</span></div>
<p>在大部分UNIX系统中，默认所有符号被导出。在Windows系统中，开发者必须明确告知编译器哪些符号<span style="background-color: #c0c0c0;">被导入（使用符号时）/导出（创建符号时）</span></p>
<p>当从UNIX移植项目到Windows平台时，你可以：</p>
<ol>
<li>创建一个额外的.def文件，或者</li>
<li>使用微软的C/C++语言扩展——<pre class="crayon-plain-tag">__declspec(dllexport)</pre> 、<pre class="crayon-plain-tag">__declspec(dllimport)</pre> 声明的符号分别被导出、导入</li>
</ol>
<p>如果一个源文件在创建、使用一个库时都需要使用，则必须使用宏来处理。CMake在Windows下构建共享库（DDL）时，会自动定义宏<pre class="crayon-plain-tag">${LIBNAME}_EXPORTS</pre> 。我们可以利用此宏：</p>
<pre class="crayon-plain-tag">#if defined(WIN32)
    #if defined(vtkCommon_EXPORTS)
        #define VTK_COMMON_EXPORT __declspec(dllexport)
    #else
        #define VTK_COMMON_EXPORT __declspec(dllimport)
    #endif
#else
    #define VTK_COMMON_EXPORT
#endif</pre>
<p>这样，VTK_COMMON_EXPORT在UNIX中为空白；在Windows下构建共享库时为__declspec(dllexport)。</p>
<p>UNIX和Windows存在一个重要的和符号需求相关的差异：<span style="background-color: #c0c0c0;">Windows上的DLL需要完全解析，也就是在创建时必须链接所有符号</span>；而<span style="background-color: #c0c0c0;">UNIX允许共享库在运行时</span>从可执行文件或者其它共享库中<span style="background-color: #c0c0c0;">获取符号</span>。因而在UNIX中，CMake会给可执行目标一个标记，<span style="background-color: #c0c0c0;">允许它被共享库调用</span>。</p>
<p>另外一个需要提及的关于C++全局对象的陷阱是，加载或者链接了C++共享库的main函数，必须基于C++的编译器来链接，否则cout之类的全局对象可能在使用时尚未初始化。</p>
<div class="blog_h3"><span class="graybg">共享库位置</span></div>
<p>由于链接到共享库的可执行文件必须在运行时能找到这些库，特殊的环境变量或者链接器标记必须被使用。</p>
<p>不同系统都提供了工具，用以查看可执行文件实际上使用的是哪个库：</p>
<ol>
<li>UNIX系统的<pre class="crayon-plain-tag">ldd</pre> 命令：显示可执行文件使用哪些库。在Mac OS X上使用<pre class="crayon-plain-tag">otool -L</pre> </li>
<li>Windows系统的<pre class="crayon-plain-tag">depends</pre> 程序，功能类似</li>
</ol>
<p>在很多UNIX系统中，可以使用环境变量<pre class="crayon-plain-tag">LD_LIBRARY_PATH</pre> 来告诉应用程序到哪里寻找库，而在Windows中，环境变量<pre class="crayon-plain-tag">PATH</pre> 同时用来寻找DLL和可执行文件。CMake会默认把运行时库的路径信息存放到可执行文件中，因此前述环境变量并不必须。但是某些时候你可能需要关闭这个特性，设置<pre class="crayon-plain-tag">CMAKE_SKIP_RPATH=false</pre> 即可。</p>
<div class="blog_h2"><span class="graybg">共享库版本化</span></div>
<p>关于soname的基础知识，参考<a href="/linux-programming-faq#soname">Linux编程知识集锦</a>。</p>
<p>CMake支持这种基于soname的版本号编码机制，只要底层平台支持soname，可以设置共享库目标的属性：</p>
<pre class="crayon-plain-tag"># VERSION，指定一个版本号，用于创建文件名
# SOVERSION，指定一个版本号，用于生成SONAME头
set_target_properties (x PROPERTIES VERSION 1.2 SOVERSION 4)</pre>
<p>设置上述属性后，安装共享库时会产生如下文件和符号链接：</p>
<pre class="crayon-plain-tag">libx.so.1.2
libx.so.4 -&gt; libx.so.1.2
libx.so -&gt; libx.so.4</pre>
<p>如果仅指定两个版本号中的一个，那么另外一个自动<span style="background-color: #c0c0c0;">与之相同</span>。 </p>
<div class="blog_h2"><span class="graybg">安装文件</span></div>
<p>软件通常被安装到和源码、构建树无关的位置上。CMake提供一个<pre class="crayon-plain-tag">install</pre> 命令，来说明一个工程如何被安装。正确使用这个命令后：</p>
<ol>
<li>对于基于Makefile的生成器，用户只需要执行<pre class="crayon-plain-tag">make install</pre> 或者<pre class="crayon-plain-tag">nmake install</pre> 即可完成安装</li>
<li>对于基于GUI的平台，例如XCode、VS，用户只需要构建INSTALL目标</li>
</ol>
<p>对install的每一次调用都会指定某些安装规则，这些规则会依据命令调用的顺序被执行。</p>
<div class="blog_h3"><span class="graybg">install命令</span></div>
<p>install命令提供了若干“签名”（类似于子命令），签名作为第一个参数传入，可用的签名包括：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>install(TARGETS...)</td>
<td>安装工程中目标对应的二进制文件</td>
</tr>
<tr>
<td>install(FILES...)</td>
<td>一般性的文件安装，包括头文件、文档、软件需要的数据文件</td>
</tr>
<tr>
<td>install(PROGRAMS...)</td>
<td>安装不是由当前工程构建的文件，例如Shell脚本，与FILES签名类似，只是文件被授予可执行权限</td>
</tr>
<tr>
<td>install(DIRECTORY...)</td>
<td>安装一个完整的目录树，例如包含了图标、图片的资源目录</td>
</tr>
<tr>
<td>install(SCRIPT...)</td>
<td>指定一个用户提供的、在安装过程中（典型的是pre-install、post-install）执行的CMake脚本</td>
</tr>
<tr>
<td>install(CODE...)</td>
<td>与SCRIPT类似，只是脚本以内联字符串形式提供</td>
</tr>
</tbody>
</table>
<p>前四个签名都用于创建文件的安装规则，需要安装的目标、目录、文件<span style="background-color: #c0c0c0;">紧接着签名列出</span>。其余和安装相关的信息，以关键字参数的形式附加，大部分签名支持以下关键字：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">关键字</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>DESTINATION</td>
<td>
<p>说明在何处放置被安装的文件，后面必须紧跟一个目录，此目录可以指定为绝对路径。如果使用相对路径，则相对于安装时指定的前缀，前缀可能由缓存条目<pre class="crayon-plain-tag">CMAKE_INSTALL_PREFIX</pre> 指定。前缀的默认值：</p>
<ol>
<li>UNIX：/usr/local</li>
<li>Windows：系统盘符:\Program Files\工程名称</li>
</ol>
</td>
</tr>
<tr>
<td>PERMISSIONS</td>
<td>
<p>说明如何设置被安装文件的权限（UNIX文件模式），仅在需要覆盖签名默认权限的情况下使用，可用的权限为：[OWNER|GROUP|WORLD][READ|WRITE|EXECUTE]、SETUID、SETGID</p>
<p>某些平台不完整支持上述权限，这种情况下自动忽略此关键字</p>
</td>
</tr>
<tr>
<td>CONFIGURATIONS</td>
<td>
<p>指定规则应用到的构建配置（Release、Debug...）的列表</p>
<p>没有应用到的构建配置，不会执行此命令调用产生的规则</p>
</td>
</tr>
<tr>
<td>COMPONENT</td>
<td>
<p>指定规则应用到的组件。某些工程把安装划分为多个组件，以便分别打包</p>
<p>例如某个工程可能包含三个组件：</p>
<ol>
<li>Runtime：包含运行软件需要的文件</li>
<li>Development：包含基于软件进行开发需要的文件</li>
<li>Documentation：包含软件的手册和帮助文档</li>
</ol>
<p>没有应用到的组件，不会执行此命令调用产生的规则</p>
<p>默认情况下，会安装所有组件，因而此关键字不产生任何影响。如果要安装特定组件，必须手工调用安装脚本</p>
</td>
</tr>
<tr>
<td>OPTIONAL</td>
<td>指示如果期望的待安装文件不存在时，不是一个错误，仅仅忽略之</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">TARGETS签名</span></div>
<p>以此签名调用install命令，以便构建过程中创建的库、可执行文件。详细调用格式为：</p>
<pre class="crayon-plain-tag">install ( TARGETS
    targets...  # 基于add_executable/add_library创建的目标的列表
    [
        # 通过TARGETS签名安装的文件可以分为三类：
        # ARCHIVE 静态库（UNIX/Cygwin/MinGW的.A、Windows的.LIB）
        #         DLL的可链接（Linkable）导入库（Cygwin/MinGW的.DLL.A、Windows的.LIB）
        # LIBRARY 可加载模块、共享库（.SO）
        # RUNTIME 可执行文件、动态链接库（.DLL）
        # 如果指定下面一行的某个关键字，则后续的关键字仅针对特定类型的文件，否则针对所有文件
        [ARCHIVE|LIBRARY|RUNTIME|FRAMEWORK|BUNDLE|PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
        [DESTINATION &lt;dir&gt;]
        [PERMISSIOS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT component]
        [OPTIONAL]
        [EXPORT &lt;export name&gt;]
        # 下面的关键字仅用于LIBRARY类型，仅针对支持namelink、版本化共享库的平台
        # 对于符号链接lib&lt;name&gt;.so -&gt; lib&lt;name&gt;.so.1，后者是soname，前者称为namelink，namelink用于在链接时-l选项找到共享库的位置
        # NAMELINK_ONLY导致仅仅共享库的namelink被安装；NAMELINK_SKIP导致除了namelink之外的文件被安装
        # 如果不指定，那么namelink、共享库的文件都被安装
        [NAMELINK_ONLY|NAMELINK_SKIP]
    ] [
        ... #仅需要针对不同类型（ARCHIVE|LIBRARY|RUNTIME...）分别设置关键字时，才会出现
    ]
)</pre>
<p>注意上面代码中关于文件分类的规则，把同属于共享库的.SO、.DLL分别划分到LIBRART、RUNTIME是有意的设计，因为Windows平台下，DLL通常和EXE存放在一个目录，这样才能确保DLL能够被找到并加载。下面的调用确保共享库目标mySharedLib产生的所有文件<span style="background-color: #c0c0c0;">在所有平台上均安装到期望的位置</span>：</p>
<pre class="crayon-plain-tag">install ( TARGETS myExecutable mySharedLib myStaticLib myPlugin
    RUNTIME DESTINATION bin             COMPONENT Runtime
    LIBRARY DESTINATION lib             COMPONENT Runtime
    ARCHIVE DESTINATION lib/myproject   Component Development    #静态库只有在二次开发时才需要
)</pre>
<div class="blog_h3"><span class="graybg">FILES签名</span></div>
<p>很多工程可能需要安装与目标无关的任何文件，这时可以使用一般目的的FILES签名：</p>
<pre class="crayon-plain-tag">install (FILES files...     #需要被安装的文件的列表，如果是相对路径，相对于当前Source目录
    DESTINATION &lt;dir&gt;       #目标位置，如果是相对路径，相对于安装Prefix
    [PERMISSIOS permissions...] #默认权限644
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT component]
    [RENAME &lt;name&gt;]  #为文件指定新的名称，要求文件列表只有一个元素
    [OPTIONAL]
)</pre>
<div class="blog_h3"><span class="graybg">PROGRAMS签名</span></div>
<p>某些工程可能安装额外的助手程序——Shell脚本或者Python脚本。这时可以使用PROGRAMS签名。此签名和FILES一样，只是默认权限为755。</p>
<div class="blog_h3"><span class="graybg">DIRECTORY签名</span></div>
<p>有时候我们需要安装包含了大量资源文件的整个目录，此时使用DIRECTORY签名： </p>
<pre class="crayon-plain-tag">install (DIRECTORY dirs...   # 需要被安装的目录的列表，如果是相对路径，相对于当前Source目录
    # 目标位置，此目录确保被创建。如果设置为share/myproject，则：
    # data/icons 被安装到/share/myproject/icons，注意输入目录的所有祖先目录被忽略
    # data/ 被安装到/share/myproject，注意结尾的斜杠，会导致此目录下所有内容被安装，因此data/类似于data/*
    DESTINATION &lt;dir&gt;   
    # 默认权限：文件与FILES一样644，目录与PROGRAMS一样755，下面两个关键字用于修改默认行为
    [FILE_PERMISSIOS permissions...]
    [DIRECTORY_PERMISSIOS permissions...]
    # 和文件来源保持一致的权限
    [USE_SOURCE_PERMISSIOS]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT component]
    [
        # 排除某些文件，或者为某些文件指定特殊的权限
        # PATTERN用于UNIX风格通配符匹配；REGEX用于正则式匹配
        [PATTERN &lt;pattern&gt; | REGEX &lt;regex&gt;]
        # 是否把匹配的文件排除，不安装
        [EXCLUDE]
        # 设置匹配文件的权限
        [PERMISSIOS permissions...]
    ]
    [
    ...  #排除或者chmod其它匹配文件
    ]
)</pre>
<div class="blog_h3"><span class="graybg">SCRIPT/CODE签名</span></div>
<p>拷贝文件到安装树下（Installation tree）不是安装过程的唯一内容，有时候需要执行特定的逻辑。这时可以使用SCRIPT或者CODE签名：</p>
<pre class="crayon-plain-tag">install(SCRIPT scr.cmake)   # scr.cmake为某个CMake脚本名称
install(CODE "message(Hello)") #直接跟着脚本内容</pre>
<p>注意脚本不是在CMakeLists.txt处理过程中，而是在安装过程中执行，因而在脚本中不能访问CMakeLists.txt定义的变量。尽管如此，<pre class="crayon-plain-tag">CMAKE_INSTALL_PREFIX</pre> 、<pre class="crayon-plain-tag">CMAKE_INSTALL_CONFIG_NAME</pre>  、<pre class="crayon-plain-tag">CMAKE_INSTALL_COMPONENT</pre> 会被设置为真实的安装前缀、构建配置、组件类型。</p>
<div class="blog_h2"><span class="graybg">安装依赖的共享库</span></div>
<p>OS自带的、第三方提供的或者工程本身生成的共享库，是某些可执行文件能够运行的前提条件。由OS提供的自然不需要额外安装；工程本身产生的库由add_library命令说明，一般通过install命令安装到系统。需要额外考虑的是<span style="background-color: #c0c0c0;">第三方库</span>。</p>
<p>CMake提供两个模块，用于简化共享库的处理。</p>
<div class="blog_h3"><span class="graybg">GetPrerequisites.cmake</span></div>
<p>使用该模块的<pre class="crayon-plain-tag">get_prerequisites()</pre> 函数，可以分析一个可执行文件的依赖。将可执行文件的路径传递给此函数，其会输出运行此文件必须的依赖库的列表，<span style="background-color: #c0c0c0;">包括传递性依赖</span>。该函数使用各平台上的Native工具：dumpbin（Windows）、otool（Mac）、ldd（Linux）进行依赖分析。</p>
<div class="blog_h3"><span class="graybg">BundleUtilities.cmake</span></div>
<p>使用该模块的<pre class="crayon-plain-tag">fixup_bundle()</pre> 函数，可以依据可执行文件的相对位置，拷贝和修复共享库（依赖）。</p>
<p>对于Mac的bundle应用，需要的共享库会被嵌入到bundle中，并调用install_name_tool生成一个自包含bundle。</p>
<p>对于Widnows，需要的共享库会被拷贝到exe所在目录，可执行文件运行时会自动寻找并加载。</p>
<p>要使用fixup_bundle()函数，首先安装某个可执行目标，然后创建一个可以在安装时执行的CMake脚本，在此脚本中调用：</p>
<pre class="crayon-plain-tag">include (BundleUtilities)
# 安装树中的可执行文件的路径
set (bundle "${CMAKE_INSTALL_PREFIX}/myExecutable@CMAKE_EXECUTABLE_SUFFIX@")
# 无法通过依赖分析到达的依赖库的列表
set (other_libs "")
# 可以寻找到前置依赖库的目录的列表
set (dirs "@LIBRARY_OUTPUT_PATH@")

# 调用
fixup_bundle("${bundle}" "${other_libs}" "${dirs}")</pre>
<div class="blog_h2"><span class="graybg">导入和导出目标</span></div>
<p>CMake 2.6开始，支持在两个CMake工程之间导入导出目标。</p>
<div class="blog_h3"><span class="graybg">导入</span></div>
<p>导入目标这一机制，用于将项目外部的磁盘文件转换为<span style="background-color: #c0c0c0;">逻辑的CMake目标</span>。在调用add_executable、add_library命令时，传递<pre class="crayon-plain-tag">IMPORTED</pre> 选项，即可定义导入目标。CMake<span style="background-color: #c0c0c0;">不会为导入目标生成构建文件</span>，导入目标仅仅用于便利的引用外部的可执行文件和库。</p>
<p>下面的例子定义了一个导入的可执行文件，仅仅将其作为命令调用：</p>
<pre class="crayon-plain-tag"># 声明一个名为generator的导入目标
add_executable(generator IMPORTED)
# 设置目标的实际位置
set_property(TARGET generator PROPERTY IMPORT_LOCATION "/path/to/generator")
# 调用自定义命令，即添加一条定制的构建规则
# 底层构建系统执行类似这样的命令/path/to/generator /project/binary/dir/generated.c
add_custom_command(OUTPUT generated.c COMMAND generator generated.c)</pre>
<p>下面的例子定义了一个导入的库，并与之链接：</p>
<pre class="crayon-plain-tag">add_library(foo IMPORTED)

# Linux
set_property(TARGET foo PROPERTY IMPORTED_LOCATION "/path/to/libfoo.a")
# Windows下需要同时导入.lib和.dll
set_property(TARGET foo PROPERTY IMPORTED_LOCATION "/path/to/libfoo.dll")
set_property(TARGET foo PROPERTY IMPORTED_IMPLIB "/path/to/libfoo.lib")
# 具有多个构建配置的库，可以作为单个目标导入
set_property(TARGET foo PROPERTY IMPORTED_LOCATION_RELEASE "/path/to/libfoo.a")
set_property(TARGET foo PROPERTY IMPORTED_LOCATION_DEBUG   "/path/to/debug/libfoo.a")
add_executable(myexe src1.c)
target_link_libraries(myexe foo)</pre>
<div class="blog_h3"><span class="graybg">导出</span></div>
<p>尽管导入机制很有用，但是作为导入者来说，你必须知道目标在磁盘的位置。</p>
<p>使用导出机制，可以在提供目标文件的同时，提供一个文件，帮助其它工程导入。联合使用<pre class="crayon-plain-tag">install(TARGETS)</pre> 和<pre class="crayon-plain-tag">install(EXPORTS)</pre> 可以在安装目标的同时，把CMake文件也安装到机器上：</p>
<pre class="crayon-plain-tag">add_executable(generator generator.c)
# EXPORT选项导致生成一个助手文件，该文件是一个CMake脚本，可以让其它工程方便的导入generator
install(TARGET generator DESTINATION lib/myporj/generators EXPORT myproj-targets)
# 安装助手文件
install(EXPORT myproj-targets DESTINATION lib/myproj)</pre>
<p>助手文件的内容可以是：</p>
<pre class="crayon-plain-tag"># get_filename_component命令拥有得到一个全路径的某个部分
# 第一个参数：结果变量；第二个参数：待解析的路径；第三个参数，需要得到的部分，可以是DIRECTORY/NAME/EXT/PATH...
# CMAKE_CURRENT_LIST_FILE当前正在处理文件的路径
get_filename_component(_self "${CMAKE_CURRENT_LIST_FILE}" PATH)
# 解析出安装前缀的绝对路径
get_filename_component(PREFIX "${_self}/../.." ABSOLUTE)
# 添加导入目标
add_executable(generator IMPORTED)
# 通过计算出的路径引用目标
set_property(TARGET generator PROPERTY IMPORTED_LOCATION "${PREFIX}/lib/myproj/generators/generator")</pre>
<p>注意上面这个脚本依据自身位置动态计算出目标位置，即使移动安装目录，也不会失效。 </p>
<p>其它工程只需要包含助手文件即可：</p>
<pre class="crayon-plain-tag">include(/lib/myproj/myproj-targets.cmake)
# generator已经导入
add_custom_command(OUTPUT generated.c COMMAND generator generated.c)</pre>
<p>注意，单个助手文件可以容纳多个目标，甚至这些目标不在同一个目录中：</p>
<pre class="crayon-plain-tag"># A/CMakeLists.txt
add_executable(generator generator.c)
install(TARGETS generator DESTINATION lib/myproj/generators EXPORT myproj-targets)
# B/CMakeLists.txt
add_library(foo STATIC foo1.c)
install(TARGETS foo DESTINATION lib EXPORT myproj-targets)  #导出为同一个EXPORT

# CMakeLists.txt
add_subdirectory(A)
add_subdirectory(B)
install(EXPORT myproj-targets DESTINATION lib/myproj)</pre>
<div class="blog_h3"><span class="graybg">从构建树导出</span></div>
<p>典型情况下，在第三方工程需要导入之前，当前工程已经构建并安装，因此导出一般是基于安装树的。</p>
<p>CMake直接从构建树导出 ，这样第三方工程可以参考构建树来导入，这样就可以避免安装当前工程了。</p>
<p>使用<pre class="crayon-plain-tag">export</pre> 命令可以直接从构建树生成一个助手文件：</p>
<pre class="crayon-plain-tag">add_executable(generator generator.c)
export (TARGETS generator FILE myproj-exports.cmake)</pre>
<p>第三方工程可以include当前工程构建树下的myproj-exprots.cmake文件，其中包含导入generator需要的全部信息。</p>
<p>这种导出方式在交叉编译场景下可以用到。</p>
<div class="blog_h1"><span class="graybg">系统探测</span></div>
<p>系统探测，即检测在其上构建的系统的各种环境信息，是构建跨平台库或者应用程序的关键因素。</p>
<div class="blog_h2"><span class="graybg">使用头文件和库</span></div>
<p>很多C/C++程序依赖于外部的库，然后在编译和链接一个工程时，如何找到已经存在的头文件和库并不容易。因为开发程序的机器，和构建并安装程序的机器中，库的安装位置可能不一样。CMake提供多种特性，辅助开发者把外部库集成到工程中。 </p>
<p>与集成外部库相关的命令包括：<pre class="crayon-plain-tag">find_library</pre> 、<pre class="crayon-plain-tag">find_path</pre> 、<pre class="crayon-plain-tag">find_program</pre> 、<pre class="crayon-plain-tag">find_package</pre> 。对于大部分C/C++库，使用前两个命令一般足够和系统上已安装的库进行链接，这两个命令分别可以用来定位库文件、头文件所在目录。举例：</p>
<pre class="crayon-plain-tag"># 寻找一个库
find_library(
        TIFF_LIBRARY
        NAMES tiff tiff2  #只需要库的basename，不需要平台特定的前缀、后缀。前面的库优先
        #额外的路径，支持Windows注册表条目，例如[HKEY_CURRENT_USER\\Software\\Path;Build1]
        PATHS /usr/local/lib /usr/lib       #前面的路径优先
)
# 寻找一般性的文件，仅支持一个待查找文件，支持多个路径
find_path(
        TIFF_INCLUDES
        tiff.h
        /usr/local/include /usr/include
)
include_directories(${TIFF_INCLUDES})
add_executable(mytiff mytiff.c)
target_link_libraries(mytiff ${TIFF_LIBRARY})</pre>
<p>注意：</p>
<ol>
<li>find_*命令总是会寻找PATH环境变量</li>
<li>find_*命令会自动创建对应的缓存条目（文件没找到的情况下值为<pre class="crayon-plain-tag">VAR-NOTFOUND</pre> ），便于用户手工修改。这样即使CMake没有找到文件，用户还可以手工的修复</li>
</ol>
<div class="blog_h2"><span class="graybg">系统属性</span></div>
<p>在跨平台软件中，应当避免使用平台特定的代码，例如：</p>
<pre class="crayon-plain-tag">// 基于系统的判断
#ifdef defined(SUN) &amp;&amp; defined(HPUX)
    foobar();
#endif</pre>
<p> 这会降低代码的可移植性，每当需要支持新的系统，都要改变代码。即便非要使用宏，也最好<span style="background-color: #c0c0c0;">使用基于特性，而不是基于系统</span>的判断。可以改造上述代码为：</p>
<pre class="crayon-plain-tag">#ifdef HAS_FOOBAR_FUNC
    forbar();
#endif</pre>
<p>通过<pre class="crayon-plain-tag">try_compile</pre> 、<pre class="crayon-plain-tag">try_run</pre> 命令，CMake可以用来自动生成类似上面的HAS_***宏定义。这些命令编译/执行一小段代码，以探测系统特性：</p>
<pre class="crayon-plain-tag">try_compile(
        HAS_FOOBAR_FUNC
        ${CMAKE_BINARY_DIR}
        ${PROJECT_SOURCE_DIR}/testFoobar.c  #尝试调用forbar()函数
)</pre>
<p>如果编译成功，则CMake变量HAS_FOOBAR_FUNC为真。我们可以通过<pre class="crayon-plain-tag">add_definitions</pre> 命令或者配置头文件（更好），来设置HAS_FOOBAR_FUNC为宏定义。</p>
<p>如果单纯的编译并不够，还需要获知探测代码的运行结果，可以使用：</p>
<pre class="crayon-plain-tag">int main() {
    union {
        int i;
        char c;
    } u;
    u.i = 65;
    exit( u.c == 'A' );
} </pre><br />
<pre class="crayon-plain-tag">try_run(
        RUN_RESULT_VAR  #尝试运行的返回结果
        COMPILE_RESULT_VAR  #编译结果
        ${CMAKE_BINARY_DIR}
        ${PROJECT_SOURCE_DIR}/Modules/TestByteOrder.c
        OUTPUTVAR OUTPUT  #运行的任何输出
)</pre>
<p>运用上述命令的运行结果，可以根据字节序的不同来定制构建过程或者设置宏定义。对于较小的测试程序，可以不特定编写文件，使用<pre class="crayon-plain-tag">file</pre> 命令即可：</p>
<pre class="crayon-plain-tag">file(WRITE ${CMAKE_BINARY_DIR}/tmp/testc "int main(){return 0;}")</pre>
<div class="blog_h3"><span class="graybg">预定义try_*宏</span></div>
<p>在CMake/Modules中预定义了若干CMake用，可简化日常工作。这些宏常常需要查看当前<pre class="crayon-plain-tag">CMAKE_REQUIRED_FLAGS</pre> 、<pre class="crayon-plain-tag">CMAKE_REQUIRED_LIBRARIES</pre> 变量的值，以便添加额外的编译标记，或者链接以测试：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">模块</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>CheckFunctionExists.cmake</td>
<td>检查一个特定的C函数是否存在于系统中。接受两个参数，第一个参数是待测试的函数名，第二个参数是存放测试结果的变量<br />该宏会查看上述两个变量</td>
</tr>
<tr>
<td>CheckIncludeFile.cmake</td>
<td>检查一个头文件是否存在于系统中。第一个参数是头文件名称，第二个参数是存放测试结果的变量，第三个参数是可选的编译标记，如果不指定，使用CMAKE_REQUIRED_FLAGS</td>
</tr>
<tr>
<td>CheckIncludeFileCXX.cmake</td>
<td>与上面类似，但是用于C++程序。第一个参数是头文件名称，第二个参数是存放测试结果的变量，第三个参数是可选的编译标记</td>
</tr>
<tr>
<td>CheckLibraryExists.cmake</td>
<td>检查一个库是否存在于系统。接受4个参数：待测试库名称、库中待测试函数名称、库的寻找位置、测试结果<br />该宏会查看上述两个变量</td>
</tr>
<tr>
<td>CheckSymbolExists.cmake</td>
<td>检查某个符号是否在头文件中定义。接受3个参数：待测试符号名称、尝试包含的头文件列表、测试结果<br />该宏会查看上述两个变量</td>
</tr>
<tr>
<td>CheckTypeSize.cmake</td>
<td>确定某个类型的长度（字节数）。接受2个参数：待测试类型、测试结果<br />该宏会查看上述两个变量</td>
</tr>
<tr>
<td>CheckVariableExists.cmake</td>
<td>检查某个全局变量是否存在。接受2个参数：待测试全局变量名称、测试结果。仅用于C变量<br />该宏会查看上述两个变量</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">查找包</span></div>
<p>CMake提供<pre class="crayon-plain-tag">find_package(Package [version])</pre> 命令来查找符合<span style="background-color: #c0c0c0;">CPack</span>包规则的软件包。</p>
<p>该命令可以在两个模式下运行：</p>
<ol>
<li>Module模式：此模式下CMake会依次扫描<pre class="crayon-plain-tag">CMAKE_MODULE_PATH</pre>、CMake安装目录。尝试寻找到一个名称为<pre class="crayon-plain-tag">Find&lt;Package&gt;.cmake</pre> 的查找模块。如果找到则加载之，并调用其来寻找目标包的全部组件。查找模块针对特定包编写，它了解此包的全部版本，能找到包的库或者其它文件。CMake提供了很多常用的查找模块</li>
<li>Config模式：如果Module模式下没有定位到查找模块，命令自动切换到Config模式（你也可以显式的调用该模式）。在该模式下，命令会寻找<span style="background-color: #c0c0c0;">包配置文件（package configuration file）</span>：目标包提供的、一个名为<pre class="crayon-plain-tag">&lt;Package&gt;Config[Version].cmake</pre> 或者<pre class="crayon-plain-tag">&lt;package&gt;-config[-version].cmake</pre> 的文件。只要给出包的名称，命令就知道从何处寻找包配置文件，可能的位置是<pre class="crayon-plain-tag">&lt;prefix&gt;/lib/&lt;package&gt;/&lt;package&gt;-config.cmake</pre> </li>
</ol>
<div class="blog_h3"><span class="graybg">内置查找模块</span></div>
<p>CMake的内置查找模块，在找到包后，一般会定义一系列的变量供当前工程使用：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量名称约定</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>&lt;PKG&gt;_INCLUDE_DIRS</td>
<td>包的头文件所在目录</td>
</tr>
<tr>
<td>&lt;PKG&gt;_LIBRARIES</td>
<td>包提供的库的完整路径</td>
</tr>
<tr>
<td>&lt;PKG&gt;_DEFINITIONS</td>
<td>使用包时，编译代码需要用的宏定义</td>
</tr>
<tr>
<td>&lt;PKG&gt;_EXECUTABLE</td>
<td>包提供的PKG工具所在目录</td>
</tr>
<tr>
<td>&lt;PKG&gt;_&lt;TOOL&gt;_EXECUTABLE</td>
<td>包提供的TOOL工具所在目录</td>
</tr>
<tr>
<td>&lt;PKG&gt;_ROOT_DIR</td>
<td>PKG包的安装根目录</td>
</tr>
<tr>
<td>&lt;PKG&gt;_VERSION_&lt;VER&gt;</td>
<td>如果PKG的VER版本被找到，则定义为真</td>
</tr>
<tr>
<td>&lt;PKG&gt;_&lt;CMP&gt;_FOUND</td>
<td>如果PKG的CMP组件被找到，则定义为真</td>
</tr>
<tr>
<td>&lt;PKG&gt;_FOUND</td>
<td>如果PKG被找到则定义为真</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">为编译传递参数</span></div>
<p>要传递参数给编译器，可以指定命令行，或者使用一个预先配置好的头文件。</p>
<div class="blog_h3"><span class="graybg">add_definitions</span></div>
<p>调用<pre class="crayon-plain-tag">add_definitions</pre> 命令，可以向编译器传递宏定义：</p>
<pre class="crayon-plain-tag">#定义一个布尔的缓存条目
option(DENIG_BUILD "Enable debug messages")
if (DEBUG_BUILD)
    #添加宏定义
    add_definitions(-DDEBUG_MSG)
endif ()</pre>
<p>如果要细粒度的控制宏定义，可以设置目录、目标、源文件的<pre class="crayon-plain-tag">COMPILE_DEFINITIONS</pre> 属性：</p>
<pre class="crayon-plain-tag">add_library(mylib src1.c src2.c)
# 可以添加APPEND选项，追加值而不是覆盖
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS A AV=1)
set_property(TARGET mylib PROPERTY COMPILE_DEFINITIONS B BV=2)
set_property(SOURCE src1.c PROPERTY COMPILE_DEFINITIONS C CV=3)
# 执行上述命令后，编译参数分别为：
# src1.c -DA -DAV=1 -DB -DBV=2 -DC -DCV=3
# src2.c -DA -DAV=1 -DB -DBV=2 
# main.c -DA -DAV=1</pre>
<div class="blog_h3"><span class="graybg">配置头文件</span></div>
<p>这种方式更可维护，大部分工程应当使用该方式。应用程序只需要引入预先配置好的头文件即可，不必编写复杂的CMake规则。</p>
<p>我们可以把头文件看作一种配置文件，而要生成配置文件，可使用<pre class="crayon-plain-tag">configure_file(input output [@ONLY])</pre> 命令，此命令需要一个“输入文件”，输入文件可以包含三种变量定义方式：</p>
<pre class="crayon-plain-tag">// 第一种方式
#cmakedefine VARIABLE
// 如果VARIABLE为真，则输出：
#define VARIABLE
// 否则输出：
/* #undef VARIABLE */

//第二种方式，直接输出变量的值。如果confgure_file命令传递@ONLY选项，则这种方式不能使用
${VARIABLE}

//第三种方式，直接输出变量的值
@VARIABLE@</pre>
<p>配置文件应当输出到二进制树，而不是源码树，避免代码污染。因为单个CMake的源码树可以供多种构建树或平台使用，它们生成的配置文件常常是不一样的。你可能需要用include_directories命令将配置文件所在目录作为头文件目录。</p>
<div class="blog_h2"><span class="graybg">创建包配置文件</span></div>
<p>除了头文件以外，<pre class="crayon-plain-tag">configure_file</pre> 命令亦可用来生成包的配置文件。在“查找包”一节我们已经讨论过，包配置文件供其它工程发现本包。</p>
<div class="blog_h1"><span class="graybg">定制命令与目标</span></div>
<p>很多时候，“构建”一个工程不仅仅是简单的编译、链接、拷贝，额外的工作——例如利用文档工具生成文档—— 需要在构建过程中完成。</p>
<p>通过定制命令和目标，CMake可以被扩展以支持任意的任务（或者说规则）。</p>
<div class="blog_h2"><span class="graybg">可移植性</span></div>
<p>定制命令时，面临的一个重要问题是可移植性：</p>
<ol>
<li>各平台上用于完成一项任务的工具不同，以复制文件为例，UNIX使用cp命令，Windows则使用copy命令</li>
<li>目标在各平台上的名字不同，例如库x在UNIX上可能叫libx.so，Windows上则叫x.dll</li>
</ol>
<p>CMake提供了两个主要工具，解决上面两个可移植性问题。</p>
<div class="blog_h3"><span class="graybg">cmake -E命令</span></div>
<p>使用<pre class="crayon-plain-tag">cmake -E arguments</pre> 调用，可以执行一些跨平台的操作，在CMakeLists.txt中可以通过定制命令来调用cmake命令，cmake这个可执行文件可以用变量<pre class="crayon-plain-tag">CMAKE_COMMAND</pre> 引用。</p>
<p>支持的操作（arguments）包括：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">操作</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>chdir dir command args</td>
<td>改变当前目录为dir然后执行指定的命令</td>
</tr>
<tr>
<td>copy file destination</td>
<td>拷贝文件</td>
</tr>
<tr>
<td>copy_if_different infile outfile</td>
<td>如果两个文件不一样，则从infile拷贝到outfile</td>
</tr>
<tr>
<td>copy_directory source destination</td>
<td>拷贝source目录（包括子目录）中全部文件到destination目录</td>
</tr>
<tr>
<td>remove file1 file2...</td>
<td>从磁盘上删除文件</td>
</tr>
<tr>
<td>echo string</td>
<td>打印到标准输出</td>
</tr>
<tr>
<td>time command args</td>
<td>运行一个命令并且计算耗时</td>
</tr>
</tbody>
</table>
<p>CMake不限制你仅使用cmake命令，事实上你可以使用任何命令，但是要注意可移植性问题。一个通用的实践是，通过<pre class="crayon-plain-tag">find_program</pre> 找到一个程序，然后在定制命令中调用之。</p>
<div class="blog_h3"><span class="graybg">系统特征变量</span></div>
<p>CMake提供了一系列预定义的变量，描述系统的特征：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>EXE_EXTENSION</td>
<td>可执行文件的扩展名，Windows平台是.exe，UNIX是空</td>
</tr>
<tr>
<td>CMAKE_CFG_INTDIR</td>
<td>诸如VS、XCode这样的开发环境，根据构建配置的不同，使用不同的子目录，例如Debug、Release<br />在一个库、可执行文件、目标文件上执行一个命令时，你往往需要知道它们的完整路径<br />改变了在UNIX上通常是<pre class="crayon-plain-tag">./</pre> 而VS则是<pre class="crayon-plain-tag">$(INTDIR)/</pre> </td>
</tr>
<tr>
<td>CMAKE_CURRENT_BINARY_DIR</td>
<td>与当前CMakeList文件关联的输出目录的完整路径<br />可能与PROJECT_BINARY_DIR（当前工程二进制树的顶级目录）不同</td>
</tr>
<tr>
<td>CMAKE_CURRENT_SOURCE_DIR</td>
<td>与当前CMakeList文件关联的源码目录的完整路径<br />可能与PROJECT_SOURCE_DIR（当前工程源码树的顶级目录）不同</td>
</tr>
<tr>
<td>EXECUTABLE_OUTPUT_PATH</td>
<td>某些工程指定可执行文件需要生成到的目录，该变量指示其完整路径</td>
</tr>
<tr>
<td>LIBRARY_OUTPUT_PATH</td>
<td>某些工程指定库文件需要生成到的目录，该变量指示其完整路径</td>
</tr>
<tr>
<td>CMAKE_SHARED_MODULE_PREFIX</td>
<td rowspan="2">共享模块文件的前后缀</td>
</tr>
<tr>
<td>CMAKE_SHARED_MODULE_SUFFIX</td>
</tr>
<tr>
<td>CMAKE_SHARED_LIBRARY_PREFIX</td>
<td rowspan="2">共享库文件的前后缀</td>
</tr>
<tr>
<td>CMAKE_SHARED_LIBRARY_SUFFIX</td>
</tr>
<tr>
<td>CMAKE_LIBRARY_PREFIX</td>
<td rowspan="2">静态库文件的前后缀</td>
</tr>
<tr>
<td>CMAKE_LIBRARY_SUFFIX</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">在目标上使用add_custom_command</span></div>
<p>add_custom_command有两个主要的签名：TARGET、OUTPUT，分别用于为目标或者文件添加额外的规则。其中TARGET签名语法如下：</p>
<pre class="crayon-plain-tag">add_custom_command(
    #目标的名称
    TARGET target  
    #执行触发时机：
    #pre_build，在目标任何依赖文件被构建之前执行
    #pre_link，在所有依赖已经构建好，但是尚未链接时执行
    #post_build，在目标已经构建好后执行
    PRE_BUILD | PRE_LINK | POST_BUILD
    # command为可执行文件的名称
    COMMAND command [ARGS arg1 arg2 ... ]
    [COMMAND command [ARGS arg1 arg2 ... ] ...]
    # 注释，在定制命令运行时打印
    [COMMENT comment]
)</pre>
<p>下面是一个例子，在目标构建好后立即拷贝之： </p>
<pre class="crayon-plain-tag">add_executable(myExe my.c)
get_target_property(EXE_LOC myExe LOCATION)
add_custom_command(
    TARGET myExe
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} ARGS -E copy ${EXE_LOC} /QC/files
)</pre>
<div class="blog_h2"><span class="graybg">使用add_custom_command生成文件</span></div>
<p>add_custom_command的另外一个用途是指定生成一个文件的规则。这种情况下，已有的用于生成目标文件的规则被替换掉。语法如下：</p>
<pre class="crayon-plain-tag">add_custom_command (
    # 指定命令运行生成的结果文件，最好指定完整路径
    OUTPUT output1 [output2 ...]
    # 需要执行的命令
    COMMAND command [ARGS [args ...]]
    [ COMMAND command [ARGS [args ...]] ...]
    # 主要用于VS
    [MAIN_DEPENDENCY depend]
    # 命令依赖于的文件，最好指定完整路径（依赖是目标则不必），这些文件中的任何一个变化后，命令都需要重新执行
    [DEPENDS [depends ...]]
    [COMMENT comment]
)</pre>
<p>在某些库的构建过程中，例如TIFF，会先编译并构建一个可执行文件，再用此可执行文件生成还有系统特定信息的源码，而此源码参与库的最终构建。这种场景下，可以使用add_custom_command来生成源码。</p>
<div class="blog_h2"><span class="graybg">添加定制目标</span></div>
<p>CMake支持除了库、可执行文件之外的，更一般概念上的目标，称为定制目标。生成文档、运行测试、更新Web服务器都可以抽象为目标。</p>
<p>要添加定制目标，需要调用下面的命令：</p>
<pre class="crayon-plain-tag">add_custom_target(
    # name为目标的名称，如果使用Makefile生成器，你可以调用make name来生成此目标
    # ALL，表示该目标包含在ALL_BUILD目标中，自动构建
    name [ALL]
    # 执行的命令
    [command arg arg ...]
    # 此目标依赖的文件的列表，最好指定完整路径（依赖是目标则不必），这些文件可以是add_custom_command(OUTPUT)生成的
    [DEPENDS dep dep ...]
)</pre>
<div class="blog_h1"><span class="graybg">使用CMake进行交叉编译</span></div>
<p>所谓交叉编译，就是指软件在一个平台（Build host）上构建，而在另外一个平台（Target Platform）上运行。目标平台往往是另外一个OS甚至没有OS，也常常使用与构建平台不一样的硬件，这导致目标平台根本不能运行开发环境。交叉编译的典型应用是嵌入式开发，程序需要在路由器、传感器之类的特殊硬件上运行。</p>
<p>交叉编译依赖于<span style="background-color: #c0c0c0;">工具链</span>。工具链是针对目标平台的一整套工具，包括<span style="background-color: #c0c0c0;">编译器、链接器</span>，以及<span style="background-color: #c0c0c0;">目标平台的全套头文件、库</span>。</p>
<p>从2.6开始，CMake完整的支持交叉编译，包括Linux-Windows交叉编译，或者PC-嵌入设备交叉编译。在交叉编译场景下，会面临以下问题：</p>
<ol>
<li>CMake无法自动的检测目标平台</li>
<li>CMake无法在默认系统目录寻找库、头文件</li>
<li>构建出来的可执行文件无法运行</li>
</ol>
<p>CMake区分构建平台、运行平台的信息，让给用户可以解决交叉编译相关的问题，避免例如运行虚拟机的额外需求。</p>
<div class="blog_h2"><span class="graybg">工具链文件</span></div>
<p>通过一个所谓工具链（Toolchain）文件，我们可以告知CMake<span style="background-color: #c0c0c0;">关于目标平台的任何必要信息</span>。CMakeList.txt必须被调整以适应目标平台和构建平台具有不同属性的情况。下面是一个Linux下基于MinGW交叉编译器，交叉编译Windows程序使用的工具链文件：</p>
<pre class="crayon-plain-tag">#目标平台的名称
set( CMAKE_SYSTEM_NAME Windows )

#指定C/C++编译器为交叉编译器，只有交叉编译器才知道如何构建目标平台上的二进制文件
set( CMAKE_C_COMPILER i586-mingw32msvc-gcc )
set( CMAKE_CXX_COMPILER i586-mingw32msvc-g++ )

#指定目标平台环境的位置，这一位置在构建平台中，但是存放的是目标平台需要的头文件、库
set( CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc /home/alex/mingw-install )

#调整find_***命令的行为
#仅在构建平台上寻找程序
set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER )
#仅在目标平台环境中寻找头文件、库
set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY )
set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY )</pre>
<p>通过下面的命令，可以指示CMake使用上述工具链文件：</p>
<pre class="crayon-plain-tag">cd src/build
cmake -DCMAKE_TOOLCHAIN_FILE=~/TC-mingw.cmake ...</pre>
<p>CMAKE_TOOLCHAIN_FILE必须在最初运行时设置，后续其值会保存为缓存条目。每个目标平台一般只需要一个工具链文件。</p>
<div class="blog_h3"><span class="graybg">工具链文件变量</span></div>
<p>在工具链文件中，可能需要设置以下变量：</p>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>CMAKE_SYSTEM_NAME</td>
<td>
<p>该变量必须设置，指示目标平台的名称。典型的名称如Linux、Windows，如果目标平台是无OS的嵌入式平台，设置为Generic。此名称会用来生成平台文件的名称，例如Linux.cmake、Windows-gcc.cmake</p>
<p>手工设置此变量后，CMake认为当前是在做交叉编译，自动设置变量<pre class="crayon-plain-tag">CMAKE_CROSSCOMPILING</pre> 为真</p>
</td>
</tr>
<tr>
<td>CMAKE_SYSTEM_VERSION</td>
<td>可选的，目标平台的版本</td>
</tr>
<tr>
<td>CMAKE_SYSTEM_PROCESSOR</td>
<td>
<p>可选的，目标平台处理器或者硬件的名称。CMake用此变量加载文件：<pre class="crayon-plain-tag">${CMAKE_SYSTEM_NAME}-COMPILER_ID-${CMAKE_SYSTEM_PROCESSOR}.cmake</pre> ，该文件用来修改设置，例如目标的编译标记</p>
<p>仅在你需要为目标平台指定特殊的编译设置时，才需要设置该变量</p>
</td>
</tr>
<tr>
<td>CMAKE_C_COMPILER</td>
<td>
<p>指定C编译器的完整路径或者名字。如果指定为完整路径，这CMake倾向于到对应目录寻找binutils、linker、C++编译器等其它内容。如果指定的编译器是一个GNU交叉编译器，则CMake会自动寻找到对应的C++编译器，例如从arm-elf-gcc找到arm-elf-c++</p>
<p>C编译器亦可通过环境变量CC设置</p>
</td>
</tr>
<tr>
<td>CMAKE_CXX_COMPILER</td>
<td>
<p>指定C++编译器的完整路径或者名字。对于GNU工具链，只需要设置CMAKE_C_COMPILER，此变量不必设置</p>
<p>C++编译器亦可通过环境变量CXX设置</p>
</td>
</tr>
<tr>
<td>CMAKE_FIND_ROOT_PATH</td>
<td>
<p>指定一组包含了目标平台环境的目录，这些目录供所有find_**命令使用</p>
<p>假设目标平台环境安装在/opt/eldk/ppc_74xx，设置变量为此路径。find_library寻找jpeg库时会最终定位到/opt/eldk/ppc_74xx/lib/libjpeg.so</p>
</td>
</tr>
<tr>
<td>CMAKE_FIND_ROOT_PATH_MODE_PROGRAM</td>
<td rowspan="3">
<p>分别设置find_program、find_library、find_include命令的默认行为。可以设置为：</p>
<ol>
<li>NEVER CMAKE_FIND_ROOT_PATH对命令无效</li>
<li>ONLY 仅在CMAKE_FIND_ROOT_PATH目录中搜索</li>
<li>BOTH 默认值，都搜索</li>
</ol>
</td>
</tr>
<tr>
<td>CMAKE_FIND_ROOT_PATH_MODE_LIBRARY</td>
</tr>
<tr>
<td>CMAKE_FIND_ROOT_PATH_MODE_INCLUDE</td>
</tr>
</tbody>
</table>
<p>注：形如CMAKE_SYSTEM_XXX的变量，总是在描述目标平台。如果要描述当前构建平台，可以使用相应的CMAKE_HOST_SYSTEM_XXX变量。</p>
<div class="blog_h2"><span class="graybg">系统探测</span></div>
<p>CMake提供了一些变量，用于粗粒度的测试系统特征：</p>
<ol>
<li>目标平台类型指示变量：UNIX、WIN32、APPLE</li>
<li>构建平台类型指示变量：CMAKE_HOST_UNIX、 CMAKE_HOST_WIN32、CMAKE_HOST_APPLE</li>
</ol>
<p>更加细化的测试变量，可以使用上节提到的CMAKE_SYSTEM_XXX、CMAKE_HOST_SYSTEM_XXX变量。</p>
<div class="blog_h3"><span class="graybg">编译检查</span></div>
<p>CMake中使用CHECK_INCLUDE_FILES、CHECK_C_SOURCE_RUNS等宏来测试平台属性，这些宏通常使用try_compile、try_run命令。</p>
<p>try_run无法正常运行，因为交叉编译出的可执行文件不能在构建平台上运行。try_run被调用时，它首先尝试编译，如果成功它会检查CMAKE_CROSSCOMPILING变量，该变量为真的话它不会尝试运行，而是设置两个缓存变量，供用户后续修改。考虑下面这个例子：</p>
<pre class="crayon-plain-tag">try_run(
        SHARED_LIBRARY_PATH_TYPE
        SHARED_LIBRARY_PATH_INFO_COMPILED
        ${PROJECT_BINARY_DIR}/CMakeTmp
        ${PROJECT_SOURCE_DIR}/CMake/SharedLPathInfo.cxx
        OUTPUT_VARIABLE OUTPUT
        ARGS "LDPATH"
)</pre>
<p>如果SharedLPathInfo.cxx编译成功，SHARED_LIBRARY_PATH_INFO_COMPILED被设置为真。而交叉编译时无法运行可执行文件，因此CMake创建一个缓存条目：<pre class="crayon-plain-tag">SHARED_LIBRARY_PATH_TYPE=PLEASE_FILL_OUT-FAILED_TO_RUN</pre> ，该条目必须被手工的设置为SharedLPathInfo的在目标平台上的退出码。如果指定了OUTPUT_VARIABLE选项，CMake还会创建一个缓存条目<pre class="crayon-plain-tag">SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT=PLEASE_FILL_OUT-NOTFOUND</pre> ，该条目必须手工设置为SharedLPathInfo在目标平台上的标准输出/错误。</p>
<p>要手工运行，可以考虑构建目录下的<pre class="crayon-plain-tag">cmTryCompileExec-SHARED_LIBRARY_PATH_TYPE</pre> 到目标平台下执行。</p>
<p>除了设置缓存条目外，还可以把运行结果记录到<pre class="crayon-plain-tag">${CMAKE_BINARY_DIR}/TryRunResults.cmake</pre> 中，该文件由CMake自动创建，其中包含<span style="background-color: #c0c0c0;">所有CMake无法确定的变量</span>，并记录可执行文件位置、源码位置、运行参数等信息。我们可以根据运行结果填充这些变量的值，然后为cmake调用缓存：</p>
<pre class="crayon-plain-tag">cmake -C ~/TryRunResults-myproj-eldk-ppc.cmake</pre>
<div class="blog_h2"><span class="graybg">交叉编译HelloWorld</span></div>
<p>本节示例一个完整的交叉编译的过程。</p>
<div class="blog_h3"><span class="graybg">寻找工具链</span></div>
<p>交叉编译的第一步是寻找合适的工具链，如果你已经安装好工具链，则可以跳过这一步。</p>
<p>不同工程——包括基于Linux的PDA和嵌入式设备厂商的——在Linux上处理交叉编译的途径不同，它们有自己的构建流程和工具链。CMake可以使用这些工具链，只要它们是基于普通文件系统的。</p>
<p>一个提供较为完整的目标平台环境的工具链套件是Embedded Linux Development Toolkit（ELDK），该套件支持ARM、PowerPC、MIPS等目标平台。ELDK或者其它工具链可以被安装在构建平台的任意位置，例如：</p>
<pre class="crayon-plain-tag"># 工具链
/home/alex/eldk-mips/usr/bin
# 目标平台环境
/home/alex/eldk-mpis/mips_4KC/</pre>
<div class="blog_h3"><span class="graybg">创建工具链文件</span></div>
<p>下载并安装好工具链后，下一步就是编写工具链文件，注意我们在上面提到过，每个工具链只需要一个这样的文件：</p>
<pre class="crayon-plain-tag">set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER /home/alex/eldk-mips/usr/bin/mips_4KC-gcc)
set(CMAKE_CXX_COMPILER /home/alex/eldk-mips/usr/bin/mips_4KC-g++)
set(CMAKE_FIND_ROOT_PATH /home/alex/eldk-mips/mips-4KC /home/alex/eldk-mips-extra-install)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE  ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY  ONLY)</pre>
<p>工具链文件可以存放在任何地方，推荐将其存放在统一的目录，方便其它工程重用。 </p>
<div class="blog_h3"><span class="graybg">工程文件</span></div>
<pre class="crayon-plain-tag">int main(){
    exit(0)
}</pre><br />
<pre class="crayon-plain-tag">project(Hello)
add_executable(Hello Hello.c)</pre>
<div class="blog_h3"><span class="graybg">执行构建</span></div>
<pre class="crayon-plain-tag">mkdir eldk-mips
cd eldk-mips
# 调用cmake，指定工具链文件
cmake -DCMAKE_TOOLCHAIN_FILE=~/CPP/cmake/toolchains/ToolChain-eldk-mips4K.cmake ..
# 生成器已经生成Makefile，可以构建
make VERBOSE=1</pre>
<div class="blog_h2"><span class="graybg">微控制器的交叉编译</span></div>
<p>不但支持具有OS的目标平台的交叉编译，CMake还可以针对那些没有OS的微控制器进行交叉编译。</p>
<p>本节的例子使用SDCC编译器，该编译器可以运行于主流系统下，支持8/16位微控制器的交叉编译。</p>
<p>首先，还是编写工具链文件：</p>
<pre class="crayon-plain-tag">set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR i8501)
set(CMAKE_C_COMPILER "D:/CPP/sdcc/bin/sdcc.exe")</pre>
<p>对于没有OS的目标平台，其系统名称设置为Generic。CMake假设Generic平台<span style="background-color: #c0c0c0;">不支持共享库</span>。 </p>
<p>很多微控制器工程不需要依赖任何外部库，因此往往不需要设置影响find_**的变量。</p>
<p>CMakeLists文件：</p>
<pre class="crayon-plain-tag"># 明确声明使用C语言，因为SDCC不支持C++
project (Blink C)
add_library(blink blink.c)
add_executable(hello main.c)JSONCPP_INCLUDE_DIRS
target_link_libraries(hello blink)</pre>
<p>执行构建：</p>
<pre class="crayon-plain-tag">rem 使用MS NMake生成器驱动构建
cmake -G "NMake Makefiles" -DCMAKE_TOOLCHAIN_FILE=D:/CPP/cmake/toolchains/Toolchain-sdcc.cmake ..
rem 执行构建
namke</pre>
<div class="blog_h1"><span class="graybg">命令</span></div>
<div class="blog_h2"><span class="graybg"><a id="cmake"></a>cmake</span></div>
<p>此命令是cmake的命令行接口</p>
<div class="blog_h3"><span class="graybg">命令格式</span></div>
<pre class="crayon-plain-tag">cmake [options] &lt;path-to-source&gt;
cmake [options] &lt;path-to-existing-build&gt;</pre>
<div class="blog_h3"><span class="graybg">命令选项</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-C &lt;initial-cache&gt;</td>
<td>预加载一个脚本，以生成缓存</td>
</tr>
<tr>
<td>-D &lt;var&gt;:&lt;type&gt;=&lt;value&gt;</td>
<td>定义一个cmake缓存条目</td>
</tr>
<tr>
<td>-U &lt;globbing_expr&gt;</td>
<td>从缓存中移除匹配的条目</td>
</tr>
<tr>
<td>-G &lt;generator-name&gt;</td>
<td>指定一个生成器（构建系统）</td>
</tr>
<tr>
<td>-T &lt;toolset-name&gt;</td>
<td>指定生成器支持的工具集</td>
</tr>
<tr>
<td>-Wno-dev</td>
<td>抑制开发者警告</td>
</tr>
<tr>
<td>-E</td>
<td>cmake命令模式</td>
</tr>
<tr>
<td>-i</td>
<td>以向导模式运行</td>
</tr>
<tr>
<td>-L[A][H]</td>
<td>列出所有非进阶缓存变量</td>
</tr>
<tr>
<td>--build &lt;dir&gt;</td>
<td>在dir中构建二进制树</td>
</tr>
<tr>
<td>--find-package</td>
<td>以类似于pkg-config的方式来查找包</td>
</tr>
<tr>
<td>--graphviz=[file]</td>
<td>生成依赖的graphviz</td>
</tr>
<tr>
<td>--system-information [file]</td>
<td>输出系统信息</td>
</tr>
<tr>
<td>--debug-trycompile</td>
<td>不删除trycompile构建树</td>
</tr>
<tr>
<td>--debug-output </td>
<td>以调试模式运行</td>
</tr>
<tr>
<td>--trace</td>
<td>以trace模式运行（更多调试信息）</td>
</tr>
<tr>
<td>--warn-uninitialized</td>
<td>对未初始化值进行警告</td>
</tr>
<tr>
<td>--warn-unused-vars</td>
<td>对未使用变量进行警告</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">有哪些最常用的变量</span></div>
<pre class="crayon-plain-tag">cmake 
    -DCMAKE_BUILD_TYPE:STRING=Debug           # 构建配置
    -DCMAKE_INSTALL_PREFIX:STRING=/usr        # 安装位置</pre>
<p>其它变量参考<a href="#useful-vars">这里</a>。 </p>
<div class="blog_h3"><span class="graybg">如何启用C++ 11支持</span></div>
<p>可以使用CMake 3.1引入的变量：</p>
<pre class="crayon-plain-tag">set(CMAKE_CXX_STANDARD 11) </pre>
<p>如果要对老版本CMake兼容，参考下面的宏：</p>
<pre class="crayon-plain-tag">macro(use_cxx11)
  if (CMAKE_VERSION VERSION_LESS "3.1")
    if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
      set (CMAKE_CXX_FLAGS "-std=gnu++11 ${CMAKE_CXX_FLAGS}")
    endif ()
  else ()
    set (CMAKE_CXX_STANDARD 11)
  endif ()
endmacro(use_cxx11)</pre>
<div class="blog_h3"><span class="graybg">如何添加C/C++宏定义 </span></div>
<p>全局性宏定义：</p>
<pre class="crayon-plain-tag"># 定义宏MACRO_NAME，注意前面的-D
add_definitions(-DMACRO_NAME)
# 赋值
add_definitions(-DMACRO_NAME=${value})</pre>
<p>针对某个目标：</p>
<pre class="crayon-plain-tag"># 仅仅能用于 add_executable() 或 add_library() 添加的目标

# 格式：
target_compile_definitions(&lt;target&gt;
  # PUBLIC、INTERFACE可以将宏定义传递给target的PUBLIC、INTERFACE条目
  &lt;INTERFACE|PUBLIC|PRIVATE&gt; [items1...]
  [&lt;INTERFACE|PUBLIC|PRIVATE&gt; [items2...] ...])

# 示例：
target_compile_definitions(hello PRIVATE A=1 B=0)

# 你也可以直接设置目标属性  COMPILE_DEFINITIONS</pre>
<div class="blog_h3"><span class="graybg">如何使用不同版本的GCC</span></div>
<p>使用环境变量：</p>
<pre class="crayon-plain-tag">export CC=/home/alex/CPP/lib/gcc/7.2.0/bin/gcc
export CXX=/home/alex/CPP/lib/gcc/7.2.0/bin/g++</pre>
<p>在基于CLion开发时，将上述内容添加到clion.sh脚本中 </p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/cmake">CMake学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/cmake/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
