SPF 实践记录

SPF(Symbolic PathFinder) 的配置以及使用记录

Symbolic (Java) PathFinder

SPF 是能够在 Java 字节码上进行符号执行的一个工具,它通过对字节码进行​​非标准解释执行(non-standard interpretation)​​,支持对包含基本类型参数(如int、long、double、boolean等)的方法进行符号执行,并进一步支持​​符号化字符串(symbolic strings)​​、数组及用户自定义数据结构。SPF 是一个 JPF(Java PathFinder) 的扩展

安装和配置

实验环境和版本信息

  • 选择 SPF 的 gradle build 和 Java 11 的版本(gradle-build-java-11 分支)
  • 操作系统:Windows Subsystem for Linux - archlinux (2.4.13)
  • openjdk version “11.0.26” 2025-01-21
  • OpenJDK Runtime Environment (build 11.0.26+4)
  • OpenJDK 64-Bit Server VM (build 11.0.26+4, mixed mode)
  • Gradle: 8.13

安装

参考:https://github.com/SymbolicPathFinder/jpf-symbc/blob/gradle-build-java-11/README.md

1
2
3
4
5
6
# 需要包含 jpf-core 子模块
git clone -b gradle-build-java-11 --recurse-submodules git@github.com:SymbolicPathFinder/jpf-symbc.git SPF
cd SPF
# build
gradle :jpf-core:buildJars
gradle :jpf-symbc:buildJars

跑一个简单的例子:

1
2
cd jpf-symbc
java -Xmx1024m -ea -jar ../jpf-core/build/RunJPF.jar ./src/examples/demo/NumericExample.jpf

输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
symbolic.min_int=-2147483648
symbolic.min_long=-9223372036854775808
symbolic.min_short=-32768
symbolic.min_byte=-128
symbolic.min_char=0
symbolic.max_int=2147483647
symbolic.max_long=9223372036854775807
symbolic.max_short=32767
symbolic.max_byte=127
symbolic.max_char=65535
symbolic.min_double=4.9E-324
symbolic.max_double=1.7976931348623157E308
JavaPathfinder core system v8.0 (rev 052390b95ebe2bca062c31e6c1e52a6f13d68d5f) - (C) 2005-2014 United States Government. All rights reserved.


====================================================== system under test
demo.NumericExample.main()

====================================================== search started: 4/10/25, 10:05 AM
[WARNING] orphan NativePeer method: jdk.internal.misc.Unsafe.getUnsafe()Lsun/misc/Unsafe;
Property Violated: PC is constraint # = 1
((a_1_SYMINT[-2147483648] + b_2_SYMINT[-2147483646]) - CONST_2) == CONST_0
Property Violated: result is  "java.lang.ArithmeticException: div by 0..."
****************************

====================================================== error 1
gov.nasa.jpf.vm.NoUncaughtExceptionsProperty
java.lang.ArithmeticException: div by 0
	at demo.NumericExample.test(NumericExample.java:26)
	at demo.NumericExample.main(NumericExample.java:34)


====================================================== snapshot #1
thread java.lang.Thread:{id:0,name:main,status:RUNNING,priority:5,isDaemon:false,lockCount:0,suspendCount:0}
  call stack:
	at demo.NumericExample.test(NumericExample.java:26)
	at demo.NumericExample.main(NumericExample.java:34)


====================================================== Method Summaries
Inputs: a_1_SYMINT,b_2_SYMINT

demo.NumericExample.test(-2147483648,-2147483646)  --> "java.lang.ArithmeticException: div by 0..."

====================================================== Method Summaries (HTML)
<h1>Test Cases Generated by Symbolic JavaPath Finder for demo.NumericExample.test (Path Coverage) </h1>
<table border=1>
<tr><td>a_1_SYMINT</td><td>b_2_SYMINT</td><td>RETURN</td></tr>
<tr><td>-2147483648</td><td>-2147483646</td><td>"java.lang.ArithmeticException: div by 0..."</td></tr>
</table>

====================================================== results
error #1: gov.nasa.jpf.vm.NoUncaughtExceptionsProperty "java.lang.ArithmeticException: div by 0  at demo.N..."

====================================================== statistics
elapsed time:       00:00:00
states:             new=3,visited=0,backtracked=3,end=0
search:             maxDepth=2,constraints=0
choice generators:  thread=1 (signal=0,lock=1,sharedRef=0,threadApi=0,reschedule=0), data=1
heap:               new=517,released=15,maxLive=0,gcCycles=1
instructions:       9176
max memory:         124MB
loaded code:        classes=85,methods=1784

====================================================== search finished: 4/10/25, 10:05 AM

简单使用

${jpf-symbc}/src/example 目录下有很多例子,可以从中研究一下 .jpf 配置文件的内容,如何处理不同的情况和需求。

jpf-symbc 的配置文件的一些内容

  • classpath 和 sourcepath 需要指定要进行测试的类的源码路径和编译后的 .class 文件的路径
  • symbolic.bp 配置使用的约束求解器,例如:symbolic.bp=z3, symbolic_bp=no_solver
  • listener 配置监听器并在最终输出哪些内容,选择哪一个 listener,listener = gov.nasa.jpf.symbc.sequences.SymbolicSequenceListener 会输出生成的测试代码等,listener = .symbc.SymbolicListener 会输出生成的测试用例等

自己编写一些 example 进行测试

examples/ 下新建文件夹 mytests,自己写用例进行测试:

examples/mytests/NonLinerEquation.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package mytests;

public class NonLinerEquation {

    public static int test (int a, int b, int c) {
        if (a > 100)
            return 200;
        int x = a * b + c;
        int y = a + b * c;
        int z = a * c + b;
        if (x > 0) {
            if (y > 0) {
                if (z > 0) {
                    return 3;
                } else {
                    return -1;
                }
            }
            else {
                return -2;
            }
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        int a =gov.nasa.jpf.symbc.Debug.makeSymbolicInteger("a");
        int b =gov.nasa.jpf.symbc.Debug.makeSymbolicInteger("b");
        int c =gov.nasa.jpf.symbc.Debug.makeSymbolicInteger("c");
        int result = test(a, b, c);
        System.out.println("Result: " + result);
    }
    
}

examples/mytests/NonLinerEquation.jpf:

1
2
3
4
5
6
7
8
9
target=mytests.NonLinerEquation
classpath=${jpf-symbc}/build/examples
sourcepath=${jpf-symbc}/src/examples
symbolic.method = mytests.NonLinerEquation.test(sym#sym#sym)
# symbolic.min_int=-100
# symbolic.max_int=100
symbolic.dp=z3
listener = gov.nasa.jpf.symbc.SymbolicListener
# listener = gov.nasa.jpf.symbc.sequences.SymbolicSequenceListener

在运行中,出现 java.lang.UnsatisfiedLinkError: no libz3java in java.library.path error,这是由于 Z3 求解器需要另外安装,通过 sudo pacman -S z3 安装之后解决。

最终输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
symbolic.min_int=-2147483648
symbolic.min_long=-9223372036854775808
symbolic.min_short=-32768
symbolic.min_byte=-128
symbolic.min_char=0
symbolic.max_int=2147483647
symbolic.max_long=9223372036854775807
symbolic.max_short=32767
symbolic.max_byte=127
symbolic.max_char=65535
symbolic.min_double=4.9E-324
symbolic.max_double=1.7976931348623157E308
JavaPathfinder core system v8.0 (rev 052390b95ebe2bca062c31e6c1e52a6f13d68d5f) - (C) 2005-2014 United States Government. All rights reserved.


====================================================== system under test
mytests.NonLinerEquation.main()

====================================================== search started: 4/10/25, 10:47 AM
[WARNING] orphan NativePeer method: jdk.internal.misc.Unsafe.getUnsafe()Lsun/misc/Unsafe;
Result: 200
Result: 1
Result: -1
Result: 10
Result: 0

====================================================== Method Summaries
Inputs: a_1_SYMINT,b_2_SYMINT,c_3_SYMINT

mytests.NonLinerEquation.test(101,-9223372036854775808(don't care),-9223372036854775808(don't care))  --> Return Value: 200
mytests.NonLinerEquation.test(1,1,0)  --> Return Value: 1
mytests.NonLinerEquation.test(4,1,-3)  --> Return Value: -1
mytests.NonLinerEquation.test(0,0,1)  --> Return Value: 10
mytests.NonLinerEquation.test(0,0,0)  --> Return Value: 0

====================================================== Method Summaries (HTML)
<h1>Test Cases Generated by Symbolic JavaPath Finder for mytests.NonLinerEquation.test (Path Coverage) </h1>
<table border=1>
<tr><td>a_1_SYMINT</td><td>b_2_SYMINT</td><td>c_3_SYMINT</td><td>RETURN</td></tr>
<tr><td>101</td><td>-9223372036854775808(don't care)</td><td>-9223372036854775808(don't care)</td><td>Return Value: 200</td></tr>
<tr><td>1</td><td>1</td><td>0</td><td>Return Value: 1</td></tr>
<tr><td>4</td><td>1</td><td>-3</td><td>Return Value: -1</td></tr>
<tr><td>0</td><td>0</td><td>1</td><td>Return Value: 10</td></tr>
<tr><td>0</td><td>0</td><td>0</td><td>Return Value: 0</td></tr>
</table>

====================================================== results
no errors detected

====================================================== statistics
elapsed time:       00:00:00
states:             new=9,visited=0,backtracked=9,end=5
search:             maxDepth=5,constraints=0
choice generators:  thread=1 (signal=0,lock=1,sharedRef=0,threadApi=0,reschedule=0), data=4
heap:               new=650,released=105,maxLive=505,gcCycles=6
instructions:       11535
max memory:         124MB
loaded code:        classes=87,methods=2345

====================================================== search finished: 4/10/25, 10:47 AM

打印 Path Condition (PC)

利用 Debug.printPC() 可以打印不同路径的条件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package mytests;

import gov.nasa.jpf.symbc.Debug;

public class CollectConstraints {
	public static void test(int x, int y) {
		int z = x + y;
		if (z != 0) {
			if (x > 0) {
				System.out.println("Visiting the branch x > 0");
			} else {
				System.out.println("Visiting the branch y < 0");
			}
		} else {
			System.out.println("Visiting the branch z == 0");
		}

		Debug.printPC("PC ");
	}

	// The test driver
	public static void main(String[] args) {
		int x = 2;
		int y = 3;
		// x = Debug.addSymbolicInt(x, "sym_x");
		// y = Debug.addSymbolicInt(y, "sym_y");
		test(x, y);
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
target=mytests.CollectConstraints
classpath=${jpf-symbc}/build/examples
sourcepath=${jpf-symbc}/src/examples
symbolic.method = mytests.CollectConstraints.test(sym#sym)
symbolic.dp=z3
symbolic.optimizechoices=false

#symbolic.debug=true

# listener = .symbc.SymbolicListener

listener = gov.nasa.jpf.symbc.sequences.SymbolicSequenceListener

进行分析,输出为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
symbolic.min_int=-2147483648
symbolic.min_long=-9223372036854775808
symbolic.min_short=-32768
symbolic.min_byte=-128
symbolic.min_char=0
symbolic.max_int=2147483647
symbolic.max_long=9223372036854775807
symbolic.max_short=32767
symbolic.max_byte=127
symbolic.max_char=65535
symbolic.min_double=4.9E-324
symbolic.max_double=1.7976931348623157E308
JavaPathfinder core system v8.0 (rev 052390b95ebe2bca062c31e6c1e52a6f13d68d5f) - (C) 2005-2014 United States Government. All rights reserved.


====================================================== system under test
mytests.CollectConstraints.main()

====================================================== search started: 4/14/25, 10:04 AM
[WARNING] orphan NativePeer method: jdk.internal.misc.Unsafe.getUnsafe()Lsun/misc/Unsafe;
Visiting the branch x > 0
PC constraint # = 2
x_1_SYMINT > CONST_0 &&
(y_2_SYMINT + x_1_SYMINT) != CONST_0
Visiting the branch y < 0
PC constraint # = 2
x_1_SYMINT <= CONST_0 &&
(y_2_SYMINT + x_1_SYMINT) != CONST_0
Visiting the branch z == 0
PC constraint # = 1
(y_2_SYMINT + x_1_SYMINT) == CONST_0

====================================================== Method Sequences
[test(1,-2)]
[test(0,-1)]
[test(0,0)]

====================================================== JUnit 4.0 test class
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

public class mytests_CollectConstraintsTest {

        private mytests.CollectConstraints mytests_collectconstraints;

        @Before
        public void setUp() throws Exception {
                mytests_collectconstraints = new mytests.CollectConstraints();
        }

        @Test
        public void test0() {
                mytests_collectconstraints.test(1,-2);
        }

        @Test
        public void test1() {
                mytests_collectconstraints.test(0,-1);
        }

        @Test
        public void test2() {
                mytests_collectconstraints.test(0,0);
        }
}

====================================================== results
no errors detected

====================================================== statistics
elapsed time:       00:00:00
states:             new=6,visited=0,backtracked=6,end=3
search:             maxDepth=4,constraints=0
choice generators:  thread=1 (signal=0,lock=1,sharedRef=0,threadApi=0,reschedule=0), data=3
heap:               new=522,released=42,maxLive=486,gcCycles=4
instructions:       9255
max memory:         124MB
loaded code:        classes=82,methods=1800

====================================================== search finished: 4/14/25, 10:04 AM

Assume

利用 Debug.assume(),可以指定条件进行分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package mytests;

import gov.nasa.jpf.symbc.Debug;

public class CollectConstraints {
	public static void test(int x, int y) {
		int z = x + y;
		Debug.assume(z != 0);
		if (z != 0) {
			if (x > 0) {
				System.out.println("Visiting the branch x > 0");
			} else {
				System.out.println("Visiting the branch y < 0");
			}
		} else {
			System.out.println("Visiting the branch z == 0");
		}

		Debug.printPC("PC ");
	}

	// The test driver
	public static void main(String[] args) {
		int x = 2;
		int y = 3;
		// x = Debug.addSymbolicInt(x, "sym_x");
		// y = Debug.addSymbolicInt(y, "sym_y");
		test(x, y);
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
symbolic.min_int=-2147483648
symbolic.min_long=-9223372036854775808
symbolic.min_short=-32768
symbolic.min_byte=-128
symbolic.min_char=0
symbolic.max_int=2147483647
symbolic.max_long=9223372036854775807
symbolic.max_short=32767
symbolic.max_byte=127
symbolic.max_char=65535
symbolic.min_double=4.9E-324
symbolic.max_double=1.7976931348623157E308
JavaPathfinder core system v8.0 (rev 052390b95ebe2bca062c31e6c1e52a6f13d68d5f) - (C) 2005-2014 United States Government. All rights reserved.


====================================================== system under test
mytests.CollectConstraints.main()

====================================================== search started: 4/14/25, 10:07 AM
[WARNING] orphan NativePeer method: jdk.internal.misc.Unsafe.getUnsafe()Lsun/misc/Unsafe;
Visiting the branch x > 0
PC constraint # = 2
x_1_SYMINT > CONST_0 &&
(y_2_SYMINT + x_1_SYMINT) != CONST_0
Visiting the branch y < 0
PC constraint # = 2
x_1_SYMINT <= CONST_0 &&
(y_2_SYMINT + x_1_SYMINT) != CONST_0

====================================================== Method Summaries
Inputs: x_1_SYMINT,y_2_SYMINT

mytests.CollectConstraints.test(1,-2)  --> Return Value: --
mytests.CollectConstraints.test(-1,0)  --> Return Value: --

====================================================== Method Summaries (HTML)
<h1>Test Cases Generated by Symbolic JavaPath Finder for mytests.CollectConstraints.test (Path Coverage) </h1>
<table border=1>
<tr><td>x_1_SYMINT</td><td>y_2_SYMINT</td><td>RETURN</td></tr>
<tr><td>1</td><td>-2</td><td>Return Value: --</td></tr>
<tr><td>-1</td><td>0</td><td>Return Value: --</td></tr>
</table>

====================================================== results
no errors detected

====================================================== statistics
elapsed time:       00:00:00
states:             new=7,visited=0,backtracked=7,end=2
search:             maxDepth=4,constraints=0
choice generators:  thread=1 (signal=0,lock=1,sharedRef=0,threadApi=0,reschedule=0), data=3
heap:               new=515,released=33,maxLive=489,gcCycles=4
instructions:       9244
max memory:         124MB
loaded code:        classes=82,methods=1800

====================================================== search finished: 4/14/25, 10:07 AM

引入其他 Java 库进行测试

需要在 gradle 的配置文件中添加依赖,要注意的是,main 源码包以外的源码包(比如example)的依赖添加需要指定源码包:

1
2
3
4
5
6
7
dependencies {
    ......
    
    examplesImplementation 'net.time4j:time4j-base:5.9.4'

    ......
}

编写对应的测试驱动类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package mytests;

import gov.nasa.jpf.symbc.Debug;
import net.time4j.base.MathUtils;

public class TestTime4J {
    public static void test(long num) {
        MathUtils.safeCast(num);

        Debug.printPC("PC ");
    }
    public static void main(String[] args) {
        long num = 0;
        test(num);
    }
}

为了方便,把 gradle 下载的 time4j 的 jar 包复制到了项目目录下,并且在 jpf 配置文件中指明 classpath, sourcepath

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
target=mytests.TestTime4J
classpath=${jpf-symbc}/build/examples:\
${jpf-symbc}/lib-to-be-tested/time4j-base-5.9.4.jar
sourcepath=${jpf-symbc}/src/examples:\
${jpf-symbc}/lib-to-be-tested/sources/time4j-base-5.9.4-sources.jar
symbolic.method = mytests.TestTime4J.test(sym)

symbolic.dp=z3
symbolic.optimizechoices=false

#symbolic.debug=true

listener = .symbc.SymbolicListener

输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
symbolic.min_int=-2147483648
symbolic.min_long=-9223372036854775808
symbolic.min_short=-32768
symbolic.min_byte=-128
symbolic.min_char=0
symbolic.max_int=2147483647
symbolic.max_long=9223372036854775807
symbolic.max_short=32767
symbolic.max_byte=127
symbolic.max_char=65535
symbolic.min_double=4.9E-324
symbolic.max_double=1.7976931348623157E308
JavaPathfinder core system v8.0 (rev 052390b95ebe2bca062c31e6c1e52a6f13d68d5f) - (C) 2005-2014 United States Government. All rights reserved.


====================================================== system under test
mytests.TestTime4J.main()

====================================================== search started: 4/14/25, 5:49 PM
[WARNING] orphan NativePeer method: jdk.internal.misc.Unsafe.getUnsafe()Lsun/misc/Unsafe;
PC constraint # = 2
num_1_SYMINT < CONST_2147483647 &&
num_1_SYMINT < CONST_-2147483648
PC constraint # = 2
num_1_SYMINT < CONST_2147483647 &&
CONST_-2147483648 == num_1_SYMINT
PC constraint # = 2
num_1_SYMINT < CONST_2147483647 &&
num_1_SYMINT > CONST_-2147483648
PC constraint # = 2
CONST_2147483647 == num_1_SYMINT &&
num_1_SYMINT > CONST_-2147483648
PC constraint # = 2
num_1_SYMINT > CONST_2147483647 &&
num_1_SYMINT > CONST_-2147483648

====================================================== Method Summaries
Inputs: num_1_SYMINT

mytests.TestTime4J.test(-2147483649l)  --> Return Value: --
mytests.TestTime4J.test(-2147483648l)  --> Return Value: --
mytests.TestTime4J.test(0l)  --> Return Value: --
mytests.TestTime4J.test(2147483647l)  --> Return Value: --
mytests.TestTime4J.test(2147483648l)  --> Return Value: --

====================================================== Method Summaries (HTML)
<h1>Test Cases Generated by Symbolic JavaPath Finder for mytests.TestTime4J.test (Path Coverage) </h1>
<table border=1>
<tr><td>num_1_SYMINT</td><td>RETURN</td></tr>
<tr><td>-2147483649</td><td>Return Value: --</td></tr>
<tr><td>-2147483648</td><td>Return Value: --</td></tr>
<tr><td>0</td><td>Return Value: --</td></tr>
<tr><td>2147483647</td><td>Return Value: --</td></tr>
<tr><td>2147483648</td><td>Return Value: --</td></tr>
</table>

====================================================== results
no errors detected

====================================================== statistics
elapsed time:       00:00:00
states:             new=13,visited=0,backtracked=13,end=5
search:             maxDepth=3,constraints=0
choice generators:  thread=1 (signal=0,lock=1,sharedRef=0,threadApi=0,reschedule=0), data=4
heap:               new=529,released=60,maxLive=489,gcCycles=6
instructions:       9313
max memory:         124MB
loaded code:        classes=83,methods=1814

====================================================== search finished: 4/14/25, 5:49 PM

不过,为了方便,选择利用 gradle 来进行这个复制 jar 包的过程,在 build.gradle 中添加 task:

1
2
3
4
task copyJars(type: Copy) {
    from configurations.examplesImplementation
    into "$buildDir/libs/jars"
}

不过,运行 task gradle spf-symbc:copyJars 之后报错:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':jpf-symbc:copyJars'.
> Resolving dependency configuration 'examplesImplementation' is not allowed as it is defined as 'canBeResolved=false'.
  Instead, a resolvable ('canBeResolved=true') dependency configuration that extends 'examplesImplementation' should be resolved.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.13/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 1s

需要创建一个可解析的配置并且继承 examplesImplementation, 在 build.gradle 中添加:

1
2
3
4
5
6
7
configurations {
    resolvableExamplesImplementation {
        canBeResolved = true  // 允许解析
        canBeConsumed = false // 禁止被其他项目消费
        extendsFrom examplesImplementation  // 继承原配置
    }
}

这样,运行 task gradle spf-symbc:copyJars 之后 gradle 就会自动将 jar 包复制到项目目录中的指定位置,不用再手动复制,在 jpf 配置文件中指定 jar 包位置等的时候会更加方便。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy