summaryrefslogtreecommitdiff
path: root/xmake/core/project/target.lua
blob: 1c568de6578e30a1fe6eb0ac267817d92b063aaa (plain)
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, Xmake Open Source Community.
--
-- @author      ruki
-- @file        target.lua
--

-- define module
local target    = target or {}
local _instance = _instance or {}

-- load modules
local bit             = require("base/bit")
local os              = require("base/os")
local path            = require("base/path")
local hash            = require("base/hash")
local utils           = require("base/utils")
local table           = require("base/table")
local baseoption      = require("base/option")
local hashset         = require("base/hashset")
local deprecated      = require("base/deprecated")
local select_script   = require("base/private/select_script")
local match_copyfiles = require("base/private/match_copyfiles")
local instance_deps   = require("base/private/instance_deps")
local is_cross        = require("base/private/is_cross")
local memcache        = require("cache/memcache")
local rule            = require("project/rule")
local option          = require("project/option")
local config          = require("project/config")
local policy          = require("project/policy")
local project_package = require("project/package")
local tool            = require("tool/tool")
local linker          = require("tool/linker")
local compiler        = require("tool/compiler")
local toolchain       = require("tool/toolchain")
local platform        = require("platform/platform")
local environment     = require("platform/environment")
local language        = require("language/language")
local sandbox         = require("sandbox/sandbox")
local sandbox_module  = require("sandbox/modules/import/core/sandbox/module")

-- new a target instance
function _instance.new(name, info)
    local instance     = table.inherit(_instance)
    instance._INFO     = info
    instance._CACHEID  = 1
    if name then
        instance:name_set(name)
    end
    return instance
end

-- get memcache
function _instance:memcache()
    local cache = self._MEMCACHE
    if not cache then
        cache = memcache.cache("core.project.target." .. tostring(self))
        self._MEMCACHE = cache
    end
    return cache
end

-- load rule, move cache to target
function _instance:_load_rule(ruleinst, suffix)

    -- init cache
    local key = ruleinst:fullname() .. (suffix and ("_" .. suffix) or "")
    local cache = self._RULES_LOADED or {}

    -- do load
    if cache[key] == nil then
        local on_load = ruleinst:script("load" .. (suffix and ("_" .. suffix) or ""))
        if on_load then
            local ok, errors = sandbox.load(on_load, self)
            cache[key] = {ok, errors}
        else
            cache[key] = {true}
        end

        -- before_load has been deprecated
        if on_load and suffix == "before" then
            deprecated.add(ruleinst:fullname() .. ".on_load", ruleinst:fullname() .. ".before_load")
        end
    end

    -- save cache
    self._RULES_LOADED = cache

    -- return results
    local results = cache[key]
    if results then
        return results[1], results[2]
    end
end

-- load rules
function _instance:_load_rules(suffix)
    for _, r in ipairs(self:orderules()) do
        local ok, errors = self:_load_rule(r, suffix)
        if not ok then
            return false, errors
        end
    end
    return true
end

-- do load target and rules
function _instance:_load()

    -- do load with target rules
    local ok, errors = self:_load_rules()
    if not ok then
        return false, errors
    end

    -- do load for target
    local on_load = self:script("load")
    if on_load then
        ok, errors = sandbox.load(on_load, self)
        if not ok then
            return false, errors
        end
    end

    -- mark as loaded
    self._LOADED = true
    return true
end

-- do before_load for rules
-- @note it's deprecated, please use on_load instead of before_load
function _instance:_load_before()
    local ok, errors = self:_load_rules("before")
    if not ok then
        return false, errors
    end
    return true
end

-- do after_load target and rules
function _instance:_load_after()

    -- enter the environments of the target packages
    local oldenvs = os.addenvs(self:pkgenvs())

    -- do load for target
    local after_load = self:script("load_after")
    if after_load then
        local ok, errors = sandbox.load(after_load, self)
        if not ok then
            return false, errors
        end
    end

    -- do after_load with target rules
    local ok, errors = self:_load_rules("after")
    if not ok then
        return false, errors
    end

    -- leave the environments of the target packages
    os.setenvs(oldenvs)
    self._LOADED_AFTER = true
    return true
end

-- get the visibility, private: 1, interface: 2, public: 3 = 1 | 2
function _instance:_visibility(opt)
    local visibility = 1
    if opt then
        if opt.interface then
            visibility = 2
        elseif opt.public then
            visibility = 3
        end
    end
    return visibility
end

-- update file rules
--
-- if we add files in on_load() dynamically, we need to update file rules,
-- otherwise it will cause: unknown source file: ...
--
function _instance:_update_filerules()
    local rulenames = {}
    local extensions = {}
    for _, sourcefile in ipairs(table.wrap(self:get("files"))) do
        local extension = path.extension((sourcefile:gsub("|.*$", "")))
        if not extensions[extension] then
            local sourcekind = self:extraconf("files", sourcefile, "sourcekind")
            local lang = sourcekind and language.load_sk(sourcekind) or language.load_ex(extension)
            if lang and lang:rules() then
                table.join2(rulenames, lang:rules())
            end
            extensions[extension] = true
        end
    end
    rulenames = table.unique(rulenames)
    for _, rulename in ipairs(rulenames) do
        local r = target._project() and target._project().rule(rulename, {namespace = self:namespace()}) or rule.rule(rulename)
        if r then
            -- only add target rules
            if r:kind() == "target" then
                if not self:rule(rulename) then
                    self:rule_add(r)
                    for _, deprule in ipairs(r:orderdeps()) do
                        if not self:rule(deprule:name()) then
                            self:rule_add(deprule)
                        end
                    end
                end
            end
        end
    end
end

-- invalidate the previous cache
function _instance:_invalidate(name)
    self._CACHEID = self._CACHEID + 1
    self._POLICIES = nil
    self:memcache():clear()
    -- we need to flush the source files cache if target/files are modified, e.g. `target:add("files", "xxx.c")`
    if name == "files" then
        self._SOURCEFILES = nil
        self._OBJECTFILES = nil
        self._SOURCEBATCHES = nil
        self:_update_filerules()
    elseif name == "deps" then
        self._DEPS = nil
        self._ORDERDEPS = nil
        self._INHERITDEPS = nil
    end
    if self._FILESCONFIG then
        self._FILESCONFIG[name] = nil
    end
end

-- build deps
function _instance:_build_deps()
    if target._project() then
        local instances   = target._project().targets()
        self._DEPS        = self._DEPS or {}
        self._ORDERDEPS   = self._ORDERDEPS or {}
        self._INHERITDEPS = self._INHERITDEPS or {}
        instance_deps.load_deps(self, instances, self._DEPS, self._ORDERDEPS, {self:fullname()})
        -- @see https://github.com/xmake-io/xmake/issues/4689
        instance_deps.load_deps(self, instances, {}, self._INHERITDEPS, {self:fullname()}, function (t, dep)
            local depinherit = t:extraconf("deps", dep:name(), "inherit")
            if depinherit == nil then
                depinherit = t:extraconf("deps", dep:fullname(), "inherit")
            end
            return depinherit == nil or depinherit
        end)
    end
end

-- is loaded?
function _instance:_is_loaded()
    return self._LOADED
end

-- get values from target deps with {interface|public = ...}
function _instance:_get_from_deps(name, result_values, result_sources, opt)
    local orderdeps = self:orderdeps({inherit = true})
    local total = #orderdeps
    for idx, _ in ipairs(orderdeps) do
        local dep = orderdeps[total + 1 - idx]
        -- We can inherit some configuration from dependencies.
        -- e.g. disable to inherit links, add_deps("foo", {links = false})
        -- @see https://github.com/xmake-io/xmake/issues/6925
        local inherit = self:extraconf("deps", dep:name(), name)
        if inherit ~= false then
            local values = dep:get(name, opt)
            if values ~= nil then
                table.insert(result_values, values)
                table.insert(result_sources, "dep::" .. dep:name())
            end
            local dep_values = {}
            local dep_sources = {}
            dep:_get_from_options(name, dep_values, dep_sources, opt)
            dep:_get_from_packages(name, dep_values, dep_sources, opt)
            for idx, values in ipairs(dep_values) do
                local dep_source = dep_sources[idx]
                table.insert(result_values, values)
                table.insert(result_sources, "dep::" .. dep:name() .. "/" .. dep_source)
            end
        end
    end
end

-- get values from target options with {interface|public = ...}
function _instance:_get_from_options(name, result_values, result_sources, opt)
    for _, opt_ in ipairs(self:orderopts(opt)) do
        local values = opt_:get(name)
        if values ~= nil then
            table.insert(result_values, values)
            table.insert(result_sources, "option::" .. opt_:name())
        end
    end
end

-- get values from target packages with {interface|public = ...}
function _instance:_get_from_packages(name, result_values, result_sources, opt)
    local function _filter_libfiles(libfiles)
        local result = {}
        for _, libfile in ipairs(table.wrap(libfiles)) do
            if not libfile:endswith(".dll") then
                table.insert(result, libfile)
            end
        end
        return table.unwrap(result)
    end
    for _, pkg in ipairs(self:orderpkgs(opt)) do
        local configinfo = self:pkgconfig(pkg:name())
        -- get values from package components
        -- e.g. `add_packages("sfml", {components = {"graphics", "window"}})`
        local selected_components = configinfo and configinfo.components or pkg:components_default()
        if selected_components and pkg:components() then
            local components_enabled = hashset.new()
            for _, comp in ipairs(table.wrap(selected_components)) do
                components_enabled:insert(comp)
                for _, dep in ipairs(table.wrap(pkg:component_orderdeps(comp))) do
                    components_enabled:insert(dep)
                end
            end
            components_enabled:insert("__base")
            -- if we can't find the values from the component, we need to fall back to __base to find them.
            -- it contains some common values of all components
            local values = {}
            local components = table.wrap(pkg:components())
            for _, component_name in ipairs(table.join(pkg:components_orderlist(), "__base")) do
                if components_enabled:has(component_name) then
                    local info = components[component_name]
                    if info then
                        local compvalues = info[name]
                        -- use full link path instead of links
                        -- @see https://github.com/xmake-io/xmake/issues/5066
                        if configinfo and configinfo.linkpath then
                            local libfiles = info.libfiles
                            if name == "links" then
                                if libfiles then
                                    compvalues = _filter_libfiles(libfiles)
                                end
                            elseif name == "linkdirs" then
                                if libfiles then
                                    compvalues = nil
                                end
                            end
                        end
                        table.join2(values, compvalues)
                    else
                        local components_str = table.concat(table.wrap(configinfo.components), ", ")
                        utils.warning("unknown component(%s) in add_packages(%s, {components = {%s}})", component_name, pkg:name(), components_str)
                    end
                end
            end
            if #values > 0 then
                table.insert(result_values, values)
                table.insert(result_sources, "package::" .. pkg:name())
            end
        -- get values instead of the builtin configs if exists extra package config
        -- e.g. `add_packages("xxx", {links = "xxx"})`
        elseif configinfo and configinfo[name] then
             local values = configinfo[name]
             if values ~= nil then
                table.insert(result_values, values)
                table.insert(result_sources, "package::" .. pkg:name())
            end
        else
            -- get values from the builtin package configs
            local values = pkg:get(name)
            -- use full link path instead of links
            -- @see https://github.com/xmake-io/xmake/issues/5066
            if configinfo and configinfo.linkpath then
                local libfiles = pkg:libraryfiles()
                if name == "links" then
                    if libfiles then
                        values = _filter_libfiles(libfiles)
                    end
                elseif name == "linkdirs" then
                    if libfiles then
                        values = nil
                    end
                end
            end
            if values ~= nil then
                table.insert(result_values, values)
                table.insert(result_sources, "package::" .. pkg:name())
            end
        end
    end
end

-- get values from the given source
function _instance:_get_from_source(name, source, result_values, result_sources, opt)
    if source == "self" then
        local values = self:get(name, opt)
        if values ~= nil then
            table.insert(result_values, values)
            table.insert(result_sources, "self")
        end
    elseif source:startswith("dep::") then
        local depname = source:split("::", {plain = true, limit = 2})[2]
        if depname == "*" then
            self:_get_from_deps(name, result_values, result_sources, opt)
        else
            local depsource
            local splitinfo = depname:split("/", {plain = true})
            if #splitinfo == 2 then
                depname = splitinfo[1]
                depsource = splitinfo[2]
            end
            local dep = self:dep(depname)
            if dep then
                -- e.g.
                -- dep::foo/option::bar
                -- dep::foo/package::bar
                if depsource then
                    local dep_values = {}
                    local dep_sources = {}
                    dep:_get_from_source(name, depsource, dep_values, dep_sources, opt)
                    for idx, values in ipairs(dep_values) do
                        local dep_source = dep_sources[idx]
                        table.insert(result_values, values)
                        table.insert(result_sources, "dep::" .. depname .. "/" .. dep_source)
                    end
                else
                    -- dep::foo
                    local values = dep:get(name, opt)
                    if values ~= nil then
                        table.insert(result_values, values)
                        table.insert(result_sources, source)
                    end
                end
            end
        end
    elseif source:startswith("option::") then
        local optname = source:split("::", {plain = true, limit = 2})[2]
        if optname == "*" then
            self:_get_from_options(name, result_values, result_sources, opt)
        else
            local opt_ = self:opt(optname, opt)
            if opt_ then
                local values = opt_:get(name)
                if values ~= nil then
                    table.insert(result_values, values)
                    table.insert(result_sources, source)
                end
            end
        end
    elseif source:startswith("package::") then
        local pkgname = source:split("::", {plain = true, limit = 2})[2]
        if pkgname == "*" then
            self:_get_from_packages(name, result_values, result_sources, opt)
        else
            local pkg = self:pkg(pkgname, opt)
            if pkg then
                local values = pkg:get(name)
                if values ~= nil then
                    table.insert(result_values, values)
                    table.insert(result_sources, source)
                end
            end
        end
    elseif source == "*" then
        self:_get_from_source(name, "self", result_values, result_sources, opt)
        self:_get_from_source(name, "option::*", result_values, result_sources, opt)
        self:_get_from_source(name, "package::*", result_values, result_sources, opt)
        self:_get_from_source(name, "dep::*", result_values, result_sources, {interface = true})
    else
        os.raise("target:get_from(): unknown source %s", source)
    end
end

-- get the checked target, it's only for target:check_xxx api.
--
-- we should not inherit links from deps/packages when checking snippets in on_config,
-- because the target deps has been not built.
--
-- @see https://github.com/xmake-io/xmake/issues/4491
--
function _instance:_checked_target()
    local checked_target = self._CHECKED_TARGET
    if checked_target == nil then
        checked_target = self:clone()
        -- we need update target:cachekey(), because target flags may be cached in builder
        checked_target:_invalidate()
        checked_target.get_from = function (target, name, sources, opt)
            if (name == "links" or name == "linkdirs") and sources == "*" then
                sources = "self"
            end
            return _instance.get_from(target, name, sources, opt)
        end
        self._CHECKED_TARGET = checked_target
    end
    return checked_target
end

-- get format
function _instance:_format(kind)
    local formats = self._FORMATS
    if not formats then
        for _, toolchain_inst in ipairs(self:toolchains()) do
            formats = toolchain_inst:formats()
            if formats then
                break
            end
        end
        self._FORMATS = formats
    end
    if formats then
        return formats[kind or self:kind()]
    end
end

-- clone target, @note we can just call it in after_load()
function _instance:clone()
    if not self:_is_loaded() then
        os.raise("please call target:clone() in after_load().", self:fullname())
    end
    local instance = target.new(self:fullname(), self._INFO:clone())
    if self._DEPS then
        instance._DEPS = table.clone(self._DEPS)
    end
    if self._ORDERDEPS then
        instance._ORDERDEPS = table.clone(self._ORDERDEPS)
    end
    if self._INHERITDEPS then
        instance._INHERITDEPS = table.clone(self._INHERITDEPS)
    end
    if self._RULES then
        instance._RULES = table.clone(self._RULES)
    end
    if self._ORDERULES then
        instance._ORDERULES = table.clone(self._ORDERULES)
    end
    if self._DATA then
        instance._DATA = table.clone(self._DATA)
    end
    if self._SOURCEFILES then
        instance._SOURCEFILES = table.clone(self._SOURCEFILES)
    end
    if self._OBJECTFILES then
        instance._OBJECTFILES = table.clone(self._OBJECTFILES)
    end
    if self._SOURCEBATCHES then
        instance._SOURCEBATCHES = table.clone(self._SOURCEBATCHES, 3)
    end
    instance._LOADED = self._LOADED
    instance._LOADED_AFTER = true
    return instance
end

-- get the target info
--
-- e.g.
--
-- default: get private
--  - target:get("cflags")
--  - target:get("cflags", {private = true})
--
-- get private and interface
--  - target:get("cflags", {public = true})
--
-- get interface
--  - target:get("cflags", {interface = true})
--
-- get raw reference of values
--  - target:get("cflags", {rawref = true})
--
function _instance:get(name, opt)

    -- get values
    local values = self._INFO:get(name)

    -- get thr required visibility
    local vs_private   = 1
    local vs_interface = 2
    local vs_public    = 3 -- all
    local vs_required  = self:_visibility(opt)

    -- get all values? (private and interface)
    if vs_required == vs_public or (opt and opt.rawref) then
        return values
    end

    -- get the extra configuration
    local extraconf = self:extraconf(name)
    if extraconf then
        -- filter values for public, private or interface if be not dictionary
        if not table.is_dictionary(values) then
            local results = {}
            for _, value in ipairs(table.wrap(values)) do
                -- we always call self:extraconf() to handle group value
                local extra = self:extraconf(name, value)
                local vs_conf = self:_visibility(extra)
                if bit.band(vs_required, vs_conf) ~= 0 then
                    table.insert(results, value)
                end
            end
            if #results > 0 then
                return table.unwrap(results)
            end
        else
            return values
        end
    else
        -- only get the private values
        if bit.band(vs_required, vs_private) ~= 0 then
            return values
        end
    end
end

-- deprecated: get values from target dependencies
function _instance:get_from_deps(name, opt)
    deprecated.add("target:get_from(%s, \"dep:*\")", "target:get_from_deps(%s)", name)
    local result = {}
    local values = self:get_from(name, "dep::*", opt)
    if values then
        for _, v in ipairs(values) do
            table.join2(result, v)
        end
    end
    return result
end

-- deprecated: get values from target options with {interface|public = ...}
function _instance:get_from_opts(name, opt)
    deprecated.add("target:get_from(%s, \"option::*\")", "target:get_from_opts(%s)", name)
    local result = {}
    local values = self:get_from(name, "option::*", opt)
    if values then
        for _, v in ipairs(values) do
            table.join2(result, v)
        end
    end
    return result
end

-- deprecated: get values from target packages with {interface|public = ...}
function _instance:get_from_pkgs(name, opt)
    deprecated.add("target:get_from(%s, \"package::*\")", "target:get_from_pkgs(%s)", name)
    local result = {}
    local values = self:get_from(name, "package::*", opt)
    if values then
        for _, v in ipairs(values) do
            table.join2(result, v)
        end
    end
    return result
end

-- get values from the given sources
--
-- e.g.
--
-- only from the current target:
--      target:get_from("links")
--      target:get_from("links", "self")
--
-- from the given dep:
--      target:get_from("links", "dep::foo")
--      target:get_from("links", "dep::foo", {interface = true})
--      target:get_from("links", "dep::*")
--
-- from the given option:
--      target:get_from("links", "option::foo")
--      target:get_from("links", "option::*")
--
-- from the given package:
--      target:get_from("links", "package::foo")
--      target:get_from("links", "package::*")
--
-- from the given dep/option, dep/package
--      target:get_from("links", "dep::foo/option::bar")
--      target:get_from("links", "dep::foo/option::*")
--      target:get_from("links", "dep::foo/package::bar")
--      target:get_from("links", "dep::foo/package::*")
--
-- from the multiple sources:
--      target:get_from("links", {"self", "option::foo", "dep::bar", "package::zoo"})
--      target:get_from("links", {"self", "option::*", "dep::*", "package::*"})
--
-- from all:
--      target:get_from("links", "*")
--
-- return:
--      local values, sources = target:get_from("links", "*")
--      for idx, value in ipairs(values) do
--          local source = sources[idx]
--      end
--
function _instance:get_from(name, sources, opt)
    local result_values = {}
    local result_sources = {}
    sources = sources or "self"
    for _, source in ipairs(table.wrap(sources)) do
        self:_get_from_source(name, source, result_values, result_sources, opt)
    end
    if #result_values > 0 then
        return result_values, result_sources
    end
end

-- set the value to the target info
--
-- @param name  the info name
-- @param ...   the values
--
function _instance:set(name, ...)
    self._INFO:apival_set(name, ...)
    self:_invalidate(name)
end

-- add the value to the target info
--
-- @param name  the info name
-- @param ...   the values to add
--
function _instance:add(name, ...)
    self._INFO:apival_add(name, ...)
    self:_invalidate(name)
end

-- remove the value to the target info (deprecated)
function _instance:del(name, ...)
    self._INFO:apival_del(name, ...)
    self:_invalidate(name)
end

-- remove the value to the target info
function _instance:remove(name, ...)
    self._INFO:apival_remove(name, ...)
    self:_invalidate(name)
end

-- get the extra configuration
function _instance:extraconf(name, item, key)
    return self._INFO:extraconf(name, item, key)
end

-- set the extra configuration
function _instance:extraconf_set(name, item, key, value)
    self._INFO:extraconf_set(name, item, key, value)
end

-- get the extra configuration from the given source
--
-- e.g.
--
-- only from the current target:
--      target:extraconf_from("links")
--      target:extraconf_from("links", "self")
--
-- from the given dep:
--      target:extraconf_from("links", "dep::foo")
--
-- from the given option:
--      target:extraconf_from("links", "option::foo")
--
-- from the given package:
--      target:extraconf_from("links", "package::foo")
--
-- from the given dep/option, dep/package
--      target:extraconf_from("links", "dep::foo/option::bar")
--      target:extraconf_from("links", "dep::foo/package::bar")
--
function _instance:extraconf_from(name, source)
    if name:find("::") then
        local tmp = name
        name = source
        source = tmp
        utils.warning("please use target:extraconf_from(%s, %s) intead of target:extraconf_from(%s, %s)", name, source, source, name)
    end
    source = source or "self"
    if source == "self" then
        return self:extraconf(name)
    elseif source:startswith("dep::") then
        local depname = source:split("::", {plain = true, limit = 2})[2]
        local depsource
        local splitinfo = depname:split("/", {plain = true})
        if #splitinfo == 2 then
            depname = splitinfo[1]
            depsource = splitinfo[2]
        end
        local dep = self:dep(depname)
        if dep then
            -- e.g.
            -- dep::foo/option::bar
            -- dep::foo/package::bar
            if depsource then
                return dep:extraconf_from(name, dep_source)
            else
                -- dep::foo
                return dep:extraconf(name)
            end
        end
    elseif source:startswith("option::") then
        local optname = source:split("::", {plain = true, limit = 2})[2]
        local opt_ = self:opt(optname, opt)
        if opt_ then
            return opt_:extraconf(name)
        end
    elseif source:startswith("package::") then
        local pkgname = source:split("::", {plain = true, limit = 2})[2]
        local pkg = self:pkg(pkgname, opt)
        if pkg then
            return pkg:extraconf(name)
        end
    else
        os.raise("target:extraconf_from(): unknown source %s", source)
    end
end

-- get configuration source information of the given api item
function _instance:sourceinfo(name, item)
    return self._INFO:sourceinfo(name, item)
end

-- get user private data
--
-- @param name  the data key
-- @return      the data value
--
function _instance:data(name)
    return self._DATA and self._DATA[name]
end

-- set user private data
--
-- @param name  the data key
-- @param data  the data value
--
function _instance:data_set(name, data)
    self._DATA = self._DATA or {}
    self._DATA[name] = data
end

-- add user private data
function _instance:data_add(name, data)
    self._DATA = self._DATA or {}
    self._DATA[name] = table.unwrap(table.join(self._DATA[name] or {}, data))
end

-- get values set by set_values/add_values
--
-- @param name       the values name, e.g. "csharp.target_framework"
-- @param sourcefile the source file (optional, for file-level values)
-- @return          the values
--
function _instance:values(name, sourcefile)

    -- get values from the source file first
    local values = {}
    if sourcefile then
        local fileconfig = self:fileconfig(sourcefile)
        if fileconfig then
            local filevalues = fileconfig.values
            if filevalues then
                -- we use '_' to simplify setting, for example:
                --
                -- add_files("xxx.mof", {values = {wdk_mof_header = "xxx.h"}})
                -- add_files("xxx.mof", {values = {["wdk.mof.header"] = "xxx.h"}})
                --
                table.join2(values, filevalues[name] or filevalues[name:gsub("%.", "_")])
            end
        end
    end

    -- get values from target
    table.join2(values, self:get("values." .. name))
    if #values > 0 then
        values = table.unwrap(values)
    else
        values = nil
    end
    return values
end

-- set values
function _instance:values_set(name, ...)
    self:set("values." .. name, ...)
end

-- add values
function _instance:values_add(name, ...)
    self:add("values." .. name, ...)
end

-- get the target info
function _instance:info()
    return self._INFO:info()
end

-- get the type: target
function _instance:type()
    return "target"
end

-- get the target name
--
-- @return      the target name string
--
function _instance:name()
    return self._NAME
end

-- set the target name
function _instance:name_set(name)
    local parts = name:split("::", {plain = true})
    self._NAME = parts[#parts]
    table.remove(parts)
    if #parts > 0 then
        self._NAMESPACE = table.concat(parts, "::")
    end
end

-- get the namespace
function _instance:namespace()
    return self._NAMESPACE
end

-- get the full name
function _instance:fullname()
    local namespace = self:namespace()
    return namespace and namespace .. "::" .. self:name() or self:name()
end

-- get the target kind, e.g. "binary", "shared", "static", "object", "headeronly"
--
-- @return      the kind string
--
function _instance:kind()
    return self:get("kind") or "binary"
end

-- get the target kind (deprecated)
function _instance:targetkind()
    return self:kind()
end

-- get the platform of this target, e.g. "windows", "linux", "macosx"
--
-- @return      the platform name
--
function _instance:plat()
    return self:get("plat") or config.get("plat") or os.host()
end

-- get the architecture of this target, e.g. "x86_64", "arm64"
--
-- @return      the architecture name
--
function _instance:arch()
    return self:get("arch") or config.get("arch") or os.arch()
end

-- is the current target belong to the given platforms?
--
-- @param ...   the platform names, e.g. "windows", "linux"
-- @return      true if matched
--
function _instance:is_plat(...)
    local plat = self:plat()
    for _, v in ipairs(table.pack(...)) do
        if v and plat == v then
            return true
        end
    end
end

-- is the current target belong to the given architectures?
--
-- @param ...   the architecture names, e.g. "x86_64", "arm64"
-- @return      true if matched
--
function _instance:is_arch(...)
    local arch = self:arch()
    for _, v in ipairs(table.pack(...)) do
        if v and arch:find("^" .. v:gsub("%-", "%%-") .. "$") then
            return true
        end
    end
end

-- is 64bits architecture?
function _instance:is_arch64()
    return self:is_arch(".+64.*")
end

-- get the platform instance
function _instance:platform()
    local platform_inst = self._PLATFORM
    if platform_inst == nil then
        platform_inst, errors = platform.load(self:plat(), self:arch())
        if not platform_inst then
            os.raise(errors)
        end
        self._PLATFORM = platform_inst
    end
    return platform_inst
end

-- get the cache key
function _instance:cachekey()
    return string.format("%s_%d", tostring(self), self._CACHEID)
end

-- get the target version
function _instance:version()
    local version = self:get("version")
    local version_build
    if version then
        version_build = self:extraconf("version", version, "build")
        if type(version_build) == "string" then
            version_build = os.date(version_build, os.time())
        end
    end
    return version, version_build
end

-- get the target soname
-- @see https://github.com/tboox/tbox/issues/214
--
-- set_version("1.0.1", {soname = "1.0"}) -> libfoo.so.1.0, libfoo.1.0.dylib
-- set_version("1.0.1", {soname = "1"}) -> libfoo.so.1, libfoo.1.dylib
-- set_version("1.0.1", {soname = true}) -> libfoo.so.1, libfoo.1.dylib
-- set_version("1.0.1", {soname = ""}) -> libfoo.so, libfoo.dylib
function _instance:soname()
    if not self:is_shared() then
        return
    end
    if self:is_plat("windows", "mingw", "cygwin", "msys") then
        return
    end
    local version = self:get("version")
    local version_soname
    if version then
        version_soname = self:extraconf("version", version, "soname")
        if version_soname == true then
            version_soname = version:split(".", {plain = true})[1]
        end
    end
    if not version_soname then
        return
    end
    local soname = self:filename()
    if type(version_soname) == "string" and #version_soname > 0 then
        local extension = path.extension(soname)
        if extension == ".dylib" then
            soname = path.basename(soname) .. "." .. version_soname .. extension
        else
            soname = soname .. "." .. version_soname
        end
    end
    return soname
end

-- get the target license
function _instance:license()
    return self:get("license")
end

-- get the target policy
function _instance:policy(name)
    local policies = self._POLICIES
    if not policies then
        policies = self:get("policy")
        self._POLICIES = policies
        if policies then
            local defined_policies = policy.policies()
            for name, _ in pairs(policies) do
                if not defined_policies[name] then
                    utils.warning("unknown policy(%s), please run `xmake l core.project.policy.policies` if you want to all policies", name)
                end
            end
        end
    end
    local value
    if policies then
        value = policies[name]
    end
    if value == nil and target._project() then
        value = target._project().policy(name)
    end
    return policy.check(name, value)
end

-- get the base name of target file
--
-- @return      the base name without extension
--
function _instance:basename()
    local filename = self:get("filename")
    if filename then
        return path.basename(filename)
    end
    return self:get("basename") or self:name()
end

-- get the target compiler
function _instance:compiler(sourcekind)
    if not sourcekind then
        os.raise("please pass sourcekind to the first argument of target:compiler(), e.g. cc, cxx, as")
    end
    local compilerinst = self:memcache():get("compiler_" .. sourcekind)
    if not compilerinst then
        local instance, errors = compiler.load(sourcekind, self)
        if not instance then
            os.raise(errors)
        end
        compilerinst = instance
        self:memcache():set("compiler_" .. sourcekind, compilerinst)
    end
    return compilerinst
end

-- get the target linker
function _instance:linker()
    local linkerinst = self:memcache():get("linker")
    if not linkerinst then
        local instance, errors = linker.load(self:kind(), self:sourcekinds(), self)
        if not instance then
            os.raise(errors)
        end
        linkerinst = instance
        self:memcache():set("linker", linkerinst)
    end
    return linkerinst
end

-- make linking command for this target
function _instance:linkcmd(objectfiles)
    return self:linker():linkcmd(objectfiles or self:objectfiles(), self:targetfile(), {target = self})
end

-- make linking arguments for this target
function _instance:linkargv(objectfiles)
    return self:linker():linkargv(objectfiles or self:objectfiles(), self:targetfile(), {target = self})
end

-- make link flags for the given target
function _instance:linkflags()
    return self:linker():linkflags({target = self})
end

-- get the given dependent target
--
-- @param name  the dependent target name
-- @return      the target instance, or nil if not found
--
function _instance:dep(name)
    local deps = self:deps()
    if deps then
        local dep = deps[name]
        if dep == nil then
            local namespace = self:namespace()
            if namespace then
                dep = deps[namespace .. "::" .. name]
            end
        end
        return dep
    end
end

-- get all dependent targets
--
-- @return      the deps table {name = target, ...}
--
function _instance:deps()
    if not self:_is_loaded() then
        os.raise("please call target:deps() or target:dep() in after_load()!")
    end
    if self._DEPS == nil then
        self:_build_deps()
    end
    return self._DEPS
end

-- get dependent targets in dependency order
--
-- @param opt   the options, e.g. {inherit = true}
-- @return      the ordered deps array
--
function _instance:orderdeps(opt)
    opt = opt or {}
    if not self:_is_loaded() then
        os.raise("please call target:orderdeps() in after_load()!")
    end
    if self._DEPS == nil then
        self:_build_deps()
    end
    return opt.inherit and self._INHERITDEPS or self._ORDERDEPS
end

-- get target rules
function _instance:rules()
    return self._RULES
end

-- get target ordered rules
function _instance:orderules()
    local rules = self._RULES
    local orderules = self._ORDERULES
    if orderules == nil and rules then
        orderules = instance_deps.sort(rules)
        self._ORDERULES = orderules
    end
    return orderules
end

-- get target rule from the given rule name
--
-- @param name  the rule name
-- @return      the rule instance, or nil if not found
--
function _instance:rule(name)
    if self._RULES then
        local r = self._RULES[name]
        if r == nil and self:namespace() then
            r = self._RULES[self:namespace() .. "::" .. name]
        end
        return r
    end
end

-- add rule
--
-- @note If a rule has the same name as a built-in rule,
-- it will be replaced in the target:rules() and target:orderules(), but will be not replaced globally in the project.rules()
function _instance:rule_add(r)
    self._RULES = self._RULES or {}
    self._RULES[r:fullname()] = r
    self._ORDERULES = nil
end

-- enable or disable rule
function _instance:rule_enable(name, enabled)
    local ruleinst = self:rule(name)
    if ruleinst then
        self:data_set("__rule_enabled." .. name, enabled)
    else
        utils.warning("target(%s): rule(%s) not found", self:name(), name)
    end
end

-- the given rule is enabled or disabled?
function _instance:rule_is_enabled(name)
    local enabled = self:data("__rule_enabled." .. name)
    return enabled ~= false
end

-- is phony target?
function _instance:is_phony()
    local targetkind = self:kind()
    return not targetkind or targetkind == "phony"
end

-- is binary target?
--
-- @return      true if the target kind is "binary"
--
function _instance:is_binary()
    return self:kind() == "binary"
end

-- is shared library target?
--
-- @return      true if the target kind is "shared"
--
function _instance:is_shared()
    return self:kind() == "shared"
end

-- is static library target?
--
-- @return      true if the target kind is "static"
--
function _instance:is_static()
    return self:kind() == "static"
end

-- is object files target?
--
-- @return      true if the target kind is "object"
--
function _instance:is_object()
    return self:kind() == "object"
end

-- is headeronly target?
--
-- @return      true if the target kind is "headeronly"
--
function _instance:is_headeronly()
    return self:kind() == "headeronly"
end

-- is moduleonly target?
function _instance:is_moduleonly()
    return self:kind() == "moduleonly"
end

-- is library target?
function _instance:is_library()
    return self:is_static() or self:is_shared() or self:is_headeronly() or self:is_moduleonly()
end

-- is default target?
function _instance:is_default()
    local default = self:get("default")
    return default == nil or default == true
end

-- is enabled?
function _instance:is_enabled()
    return self:get("enabled") ~= false
end

-- is rebuilt?
function _instance:is_rebuilt()
    return self:data("rebuilt")
end

-- is cross-compilation?
function _instance:is_cross()
    return is_cross(self:plat(), self:arch())
end

-- get the enabled option
function _instance:opt(name, opt)
    return self:opts(opt)[name]
end

-- get the enabled options
function _instance:opts(opt)
    opt = opt or {}
    local cachekey = "opts"
    if opt.public then
        cachekey = cachekey .. "_public"
    elseif opt.interface then
        cachekey = cachekey .. "_interface"
    end
    local opts = self:memcache():get(cachekey)
    if not opts then
        opts = {}
        for _, opt_ in ipairs(self:orderopts(opt)) do
            opts[opt_:name()] = opt_
        end
        self:memcache():set(cachekey, opts)
    end
    return opts
end

-- get the enabled ordered options with {public|interface = ...}
function _instance:orderopts(opt)
    opt = opt or {}
    local cachekey = "orderopts"
    if opt.public then
        cachekey = cachekey .. "_public"
    elseif opt.interface then
        cachekey = cachekey .. "_interface"
    end
    local orderopts = self:memcache():get(cachekey)
    if not orderopts then
        orderopts = {}
        for _, name in ipairs(table.wrap(self:get("options", opt))) do
            local opt_ = nil
            local enabled = config.get(name)
            if enabled == nil and self:namespace() then
                enabled = config.get(self:namespace() .. "::" .. name)
            end
            if enabled then
                opt_ = option.load(name, {namespace = self:namespace()})
            end
            if opt_ then
                table.insert(orderopts, opt_)
            end
        end
        self:memcache():set(cachekey, orderopts)
    end
    return orderopts
end

-- get the enabled package by name
--
-- @param name  the package name
-- @param opt   the options (optional)
-- @return      the package instance, or nil if not found
--
function _instance:pkg(name, opt)
    return self:pkgs(opt)[name]
end

-- get all enabled packages
--
-- @param opt   the options (optional)
-- @return      the packages table {name = package, ...}
--
function _instance:pkgs(opt)
    opt = opt or {}
    local cachekey = "pkgs"
    if opt.public then
        cachekey = cachekey .. "_public"
    elseif opt.interface then
        cachekey = cachekey .. "_interface"
    end
    local packages = self:memcache():get(cachekey)
    if not packages then
        packages = {}
        for _, pkg in ipairs(self:orderpkgs(opt)) do
            packages[pkg:name()] = pkg
        end
        self:memcache():set(cachekey, packages)
    end
    return packages
end

-- get the required packages in order
--
-- @param opt   the options (optional)
-- @return      the ordered packages array
--
function _instance:orderpkgs(opt)
    opt = opt or {}
    local cachekey = "orderpkgs"
    if opt.public then
        cachekey = cachekey .. "_public"
    elseif opt.interface then
        cachekey = cachekey .. "_interface"
    end
    local packages = self:memcache():get(cachekey)
    if not packages then
        packages = {}
        local requires = target._project().required_packages()
        if requires then
            for _, packagename in ipairs(table.wrap(self:get("packages", opt))) do
                local pkg = requires[packagename]
                -- attempt to get package with namespace
                if pkg == nil and packagename:find("::", 1, true) then
                    local parts = packagename:split("::", {plain = true})
                    local namespace_pkg = requires[parts[#parts]]
                    if namespace_pkg and namespace_pkg:namespace() then
                        local fullname = namespace_pkg:fullname()
                        if fullname:endswith(packagename) then
                            pkg = namespace_pkg
                        end
                    end
                end
                if pkg and pkg:enabled() then
                    table.insert(packages, pkg)
                end
            end
        end
        self:memcache():set(cachekey, packages)
    end
    return packages
end

-- get the environments of packages
function _instance:pkgenvs()
    local pkgenvs = self._PKGENVS
    if pkgenvs == nil then
        local pkgs = hashset.new()
        for _, pkgname in ipairs(table.wrap(self:get("packages"))) do
            local pkg = self:pkg(pkgname)
            if pkg then
                pkgs:insert(pkg)
            end
        end
        -- we can also get package envs from deps (public package)
        -- @see https://github.com/xmake-io/xmake/issues/2729
        for _, dep in ipairs(self:orderdeps()) do
            for _, pkgname in ipairs(table.wrap(dep:get("packages", {interface = true}))) do
                local pkg = dep:pkg(pkgname)
                if pkg then
                    pkgs:insert(pkg)
                end
            end
        end
        for _, pkg in pkgs:orderkeys() do
            local envs = pkg:envs()
            if envs then
                for name, values in table.orderpairs(envs) do
                    if type(values) == "table" then
                        values = path.joinenv(values)
                    end
                    pkgenvs = pkgenvs or {}
                    if pkgenvs[name] then
                        pkgenvs[name] = pkgenvs[name] .. path.envsep() .. values
                    else
                        pkgenvs[name] = values
                    end
                end
            end
        end
        self._PKGENVS = pkgenvs or false
    end
    return pkgenvs or nil
end

-- get the config info of the given package
function _instance:pkgconfig(pkgname)
    local extra_packages = self:extraconf("packages")
    if extra_packages then
        return extra_packages[pkgname]
    end
end

-- get the object files directory
--
-- @param opt   the options (optional)
-- @return      the object directory path
--
function _instance:objectdir(opt)

    -- the object directory
    local objectdir = self:get("objectdir")
    if not objectdir then
        objectdir = path.join(config.builddir(), ".objs")
    end
    local namespace = self:namespace()
    if namespace then
        objectdir = path.join(objectdir, (namespace:replace("::", path.sep())), self:name())
    else
        objectdir = path.join(objectdir, self:name())
    end

    -- get root directory of target
    local intermediate_directory = self:policy("build.intermediate_directory")
    if (opt and opt.root) or intermediate_directory == false then
        return objectdir
    end

    -- generate intermediate directory
    local plat = self:plat()
    if plat then
        objectdir = path.join(objectdir, plat)
    end
    local arch = self:arch()
    if arch then
        objectdir = path.join(objectdir, arch)
    end
    local mode = config.mode()
    if mode then
        objectdir = path.join(objectdir, mode)
    end
    return objectdir
end

-- get the dependent files directory
function _instance:dependir(opt)

    -- init the dependent directory
    local dependir = self:get("dependir")
    if not dependir then
        dependir = path.join(config.builddir(), ".deps")
    end
    local namespace = self:namespace()
    if namespace then
        dependir = path.join(dependir, (namespace:replace("::", path.sep())), self:name())
    else
        dependir = path.join(dependir, self:name())
    end

    -- get root directory of target
    local intermediate_directory = self:policy("build.intermediate_directory")
    if (opt and opt.root) or intermediate_directory == false then
        return dependir
    end

    -- generate intermediate directory
    local plat = self:plat()
    if plat then
        dependir = path.join(dependir, plat)
    end
    local arch = self:arch()
    if arch then
        dependir = path.join(dependir, arch)
    end
    local mode = config.mode()
    if mode then
        dependir = path.join(dependir, mode)
    end
    return dependir
end

-- get the auto-generated files directory
--
-- @param opt   the options (optional)
-- @return      the autogen directory path
--
function _instance:autogendir(opt)

    -- init the autogen directory
    local autogendir = self:get("autogendir")
    if not autogendir then
        autogendir = path.join(config.builddir(), ".gens")
    end
    local namespace = self:namespace()
    if namespace then
        autogendir = path.join(autogendir, (namespace:replace("::", path.sep())), self:name())
    else
        autogendir = path.join(autogendir, self:name())
    end

    -- get root directory of target
    local intermediate_directory = self:policy("build.intermediate_directory")
    if (opt and opt.root) or intermediate_directory == false then
        return autogendir
    end

    -- generate intermediate directory
    local plat = self:plat()
    if plat then
        autogendir = path.join(autogendir, plat)
    end
    local arch = self:arch()
    if arch then
        autogendir = path.join(autogendir, arch)
    end
    local mode = config.mode()
    if mode then
        autogendir = path.join(autogendir, mode)
    end
    return autogendir
end

-- get the autogen file path from the given source file path
function _instance:autogenfile(sourcefile, opt)

    -- get relative directory in the autogen directory
    local relativedir = nil
    local origindir  = path.directory(path.absolute(sourcefile))
    local autogendir = path.absolute(self:autogendir())
    if origindir:startswith(autogendir) then
        relativedir = path.join("gens", path.relative(origindir, autogendir))
    end

    -- get relative directory in the source directory
    if not relativedir then
        relativedir = path.directory(sourcefile)
    end

    -- translate path
    --
    -- e.g.
    --
    -- src/xxx.c
    --      project/xmake.lua
    --          build/.objs
    --          build/.gens
    --
    -- objectfile: project/build/.objs/xxxx/../../xxx.c will be out of range for objectdir
    -- autogenfile: project/build/.gens/xxxx/../../xxx.c will be out of range for autogendir
    --
    -- we need to replace '..' with '__' in this case
    --
    if path.is_absolute(relativedir) and os.host() == "windows" then
        -- remove C:\\ and whitespaces and fix long path issue
        -- e.g. C:\\Program Files (x64)\\xxx\Windows.h
        --
        -- @see
        -- https://github.com/xmake-io/xmake/issues/3021
        -- https://github.com/xmake-io/xmake/issues/3715
        relativedir = hash.strhash128(relativedir)
    end
    relativedir = relativedir:gsub("%.%.", "__")
    local rootdir = (opt and opt.rootdir) and opt.rootdir or self:autogendir()
    if relativedir ~= "." then
        rootdir = path.join(rootdir, relativedir)
    end
    return path.join(rootdir, (opt and opt.filename) and opt.filename or path.filename(sourcefile))
end

-- get the default target directory
function _instance:_default_targetdir()
    local targetdir = config.builddir()

    -- get root directory of target
    local intermediate_directory = self:policy("build.intermediate_directory")
    if intermediate_directory == false then
        return targetdir
    end

    -- generate intermediate directory
    local plat = self:plat()
    if plat then
        targetdir = path.join(targetdir, plat)
    end
    local arch = self:arch()
    if arch then
        targetdir = path.join(targetdir, arch)
    end
    local mode = config.mode()
    if mode then
        targetdir = path.join(targetdir, mode)
    end
    local namespace = self:namespace()
    if namespace then
        targetdir = path.join(targetdir, (namespace:replace("::", path.sep())))
    end
    return targetdir
end

-- get the target output directory
--
-- @return      the target directory path
--
function _instance:targetdir()
    local targetdir = self:get("targetdir")
    if not targetdir then
        return self:_default_targetdir()
    end

    -- we can use `set_targetdir("xxx", {bindir = "", libdir = ""})` to set sub-directory
    local subdir_kind
    if self:is_binary() or (self:is_shared() and self:is_plat("windows", "mingw")) then
        subdir_kind = "bindir"
    elseif self:is_static() or self:is_shared() then
        subdir_kind = "libdir"
    end
    return self:_artifactdir(subdir_kind)
end

-- get the build artifact output directory,
--
-- @param subdir_kind  the sub-directory kind, e.g. libdir, bindir, includedir
--
function _instance:_artifactdir(subdir_kind)
    local targetdir = self:get("targetdir")
    if not targetdir then
        return self:_default_targetdir()
    end

    if subdir_kind then
        local subdir = self:extraconf("targetdir", targetdir, subdir_kind)
        if subdir then
            return path.join(targetdir, subdir)
        end
    end
    return targetdir
end

-- get the extra build artifact file
--
-- supported artifact kinds:
--    1. implib: windows DLL/EXE implib(.lib, .dll.a)
--
-- otherwise returns nil
--
function _instance:artifactfile(kind)
    if kind == "implib" then
        if (self:is_shared() or self:is_binary()) and self:is_plat("windows", "mingw") then
            return path.join(self:_artifactdir("libdir"), path.basename(self:filename()) .. (self:is_plat("mingw") and ".dll.a" or ".lib"))
        end
    end
end

-- get the target file name (with prefix, extension)
--
-- @return      the file name string, e.g. "libfoo.a", "foo.exe"
--
function _instance:filename()

    -- no target file?
    if self:is_object() or self:is_phony() or self:is_headeronly() or self:is_moduleonly() then
        return
    end

    -- make the target file name and attempt to use the format of linker first
    local targetkind = self:targetkind()
    local filename = self:get("filename")
    if not filename then
        local prefixname = self:get("prefixname")
        local suffixname = self:get("suffixname")
        local extension  = self:get("extension")
        filename = target.filename(self:basename(), targetkind, {
            format = self:_format(),
            plat = self:plat(), arch = self:arch(),
            prefixname = prefixname,
            suffixname = suffixname,
            extension = extension})
    end
    return filename
end

-- get the link name for static/shared library
--
-- @return      the link name string, e.g. "foo" for libfoo.a
--
function _instance:linkname()
    if self:is_static() or self:is_shared() then
        local filename = self:get("filename")
        if filename then
            return target.linkname(filename)
        else
            local linkname = self:basename()
            local suffixname = self:get("suffixname")
            if suffixname then
                linkname = linkname .. suffixname
            end
            return linkname
        end
    end
end

-- get the target file full path
--
-- @return      the target file path
--
function _instance:targetfile()
    local filename = self:filename()
    if filename then
        return path.join(self:targetdir(), filename)
    end
end

-- get the symbol file
function _instance:symbolfile()

    -- the target directory
    local targetdir = self:targetdir()
    assert(targetdir and type(targetdir) == "string")

    -- the symbol file name
    local prefixname = self:get("prefixname")
    local suffixname = self:get("suffixname")
    local filename = target.filename(self:basename(), "symbol", {
        plat = self:plat(), arch = self:arch(),
        format = self:_format("symbol"),
        prefixname = prefixname,
        suffixname = suffixname})
    assert(filename)

    -- make the symbol file path
    return path.join(targetdir, filename)
end

-- get the script directory of xmake.lua
function _instance:scriptdir()
    return self:get("__scriptdir")
end

-- get configuration output directory
function _instance:configdir()
    return self:get("configdir") or config.builddir()
end

-- get run directory
function _instance:rundir()
    return baseoption.get("workdir") or self:get("rundir") or path.directory(self:targetfile())
end

-- get prefix directory
function _instance:prefixdir()
    return self:get("prefixdir")
end

-- get the installed binary directory
--
-- @return      the binary install directory path
--
function _instance:bindir()
    local bindir = baseoption.get("bindir")
    if bindir then
        return path.is_absolute(bindir) and path.normalize(bindir) or self:installdir(bindir)
    end
    bindir = self:extraconf("prefixdir", self:prefixdir(), "bindir")
    if bindir == nil then
        bindir = "bin"
    end
    return self:installdir(bindir)
end

-- get the installed library directory
--
-- @return      the library install directory path
--
function _instance:libdir()
    local libdir = baseoption.get("libdir")
    if libdir then
        return path.is_absolute(libdir) and path.normalize(libdir) or self:installdir(libdir)
    end
    libdir = self:extraconf("prefixdir", self:prefixdir(), "libdir")
    if libdir == nil then
        libdir = "lib"
    end
    return self:installdir(libdir)
end

-- get the installed include directory
function _instance:includedir()
    local includedir = baseoption.get("includedir")
    if includedir then
        return path.is_absolute(includedir) and path.normalize(includedir) or self:installdir(includedir)
    end
    includedir = self:extraconf("prefixdir", self:prefixdir(), "includedir")
    if includedir == nil then
        includedir = "include"
    end
    return self:installdir(includedir)
end

-- get the install directory
--
-- @param ...   the subdirectory components (optional)
-- @return      the install directory path
--
function _instance:installdir(...)
    opt = opt or {}
    local installdir = baseoption.get("installdir")
    if not installdir then
        -- DESTDIR: be compatible with https://www.gnu.org/prep/standards/html_node/DESTDIR.html
        installdir = self:get("installdir") or os.getenv("INSTALLDIR") or os.getenv("PREFIX") or os.getenv("DESTDIR") or platform.get("installdir")
        if installdir then
            installdir = installdir:trim()
        end
    end
    if installdir then
        local prefixdir = self:prefixdir()
        if prefixdir then
            installdir = path.join(installdir, prefixdir)
        end
        return path.normalize(path.join(installdir, ...))
    end
end

-- get package directory
function _instance:packagedir()
    -- get the output directory
    local outputdir   = baseoption.get("outputdir") or config.builddir()
    local packagename = self:name():lower()
    if #packagename > 1 and bit.band(packagename:byte(2), 0xc0) == 0x80 then
        utils.warning("package(%s): cannot generate package, becauese it contains unicode characters!", packagename)
        return
    end
    return path.join(outputdir, "packages", packagename:sub(1, 1), packagename)
end

-- get rules of the source file
function _instance:filerules(sourcefile)

    -- add rules from file config
    local rules = {}
    local override = false
    local fileconfig = self:fileconfig(sourcefile)
    if fileconfig then
        local filerules = fileconfig.rules or fileconfig.rule
        if filerules then
            override = filerules.override
            for _, rulename in ipairs(table.wrap(filerules)) do
                local r = target._project().rule(rulename, {namespace = self:namespace()}) or
                            rule.rule(rulename) or self:rule(rulename)
                if r then
                    table.insert(rules, r)
                end
            end
        end
    end
    -- override? e.g. add_files("src/*.c", {rules = {"xxx", override = true}})
    if override then
        return rules, true
    end

    -- load all rules for this target with sourcekinds and extensions
    local key2rules = self:memcache():get("key2rules")
    if not key2rules then
        key2rules = {}
        for _, r in pairs(table.wrap(self:rules())) do
            -- we can also get sourcekinds from add_rules("xxx", {sourcekinds = "cxx"})
            local rule_sourcekinds = self:extraconf("rules", r:name(), "sourcekinds") or r:get("sourcekinds")
            for _, sourcekind in ipairs(table.wrap(rule_sourcekinds)) do
                key2rules[sourcekind] = key2rules[sourcekind] or {}
                table.insert(key2rules[sourcekind], r)
            end
            -- we can also get extensions from add_rules("xxx", {extensions = ".cpp"})
            local rule_extensions = self:extraconf("rules", r:name(), "extensions") or r:get("extensions")
            for _, extension in ipairs(table.wrap(rule_extensions)) do
                extension = extension:lower()
                key2rules[extension] = key2rules[extension] or {}
                table.insert(key2rules[extension], r)
            end
        end
        self:memcache():set("key2rules", key2rules)
    end

    -- get target rules from the given sourcekind or extension
    --
    -- @note we prefer to use rules with extension because we need to be able to
    -- override the language code rules set by set_sourcekinds
    --
    -- e.g. set_extensions(".bpf.c") will override c++ rules
    --
    local rules_override = {}
    local filename = path.filename(sourcefile):lower()
    for _, r in ipairs(table.wrap(key2rules[path.extension(filename, 2)] or
                                  key2rules[path.extension(filename)] or
                                  key2rules[self:sourcekind_of(filename)] or
                                  key2rules[fileconfig and fileconfig.sourcekind])) do -- add_files("*.nasm", {sourcekind = "asm"})
        if self:extraconf("rules", r:name(), "override") then
            table.insert(rules_override, r)
        else
            table.insert(rules, r)
        end
    end

    -- we will use overridden rules first, e.g. add_rules("xxx", {override = true})
    return #rules_override > 0 and rules_override or rules
end

-- get the config info of the given source file
function _instance:fileconfig(sourcefile, opt)
    opt = opt or {}
    local filetype = opt.filetype or "files"

    -- get configs from user, e.g. target:fileconfig_set/add
    -- it has contained all original configs
    if self._FILESCONFIG_USER then
        local filesconfig = self._FILESCONFIG_USER[filetype]
        if filesconfig and filesconfig[sourcefile] then
            return filesconfig[sourcefile]
        end
    end

    -- get orignal configs from `add_xxxfiles()`
    self._FILESCONFIG = self._FILESCONFIG or {}
    local filesconfig = self._FILESCONFIG[filetype]
    if not filesconfig then
        filesconfig = {}
        for filepath, fileconfig in pairs(table.wrap(self:extraconf(filetype))) do
            local results = os.match(filepath)
            if #results > 0 then
                for _, file in ipairs(results) do
                    if path.is_absolute(file) then
                        file = path.relative(file, os.projectdir())
                    end
                    filesconfig[file] = fileconfig
                end
            else
                -- we also need support always_added, @see https://github.com/xmake-io/xmake/issues/1634
                if fileconfig.always_added then
                    filesconfig[filepath] = fileconfig
                end
            end
        end
        self._FILESCONFIG[filetype] = filesconfig
    end
    return filesconfig[sourcefile]
end

-- set the config info to the given source file
function _instance:fileconfig_set(sourcefile, info, opt)
    opt = opt or {}
    self._FILESCONFIG_USER = self._FILESCONFIG_USER or {}
    local filetype = opt.filetype or "files"
    local filesconfig = self._FILESCONFIG_USER[filetype]
    if not filesconfig then
        filesconfig = {}
        self._FILESCONFIG_USER[filetype] = filesconfig
    end
    filesconfig[sourcefile] = info
end

-- add the config info to the given source file
function _instance:fileconfig_add(sourcefile, info, opt)
    opt = opt or {}
    self._FILESCONFIG_USER = self._FILESCONFIG_USER or {}
    local filetype = opt.filetype or "files"
    local filesconfig = self._FILESCONFIG_USER[filetype]
    if not filesconfig then
        filesconfig = {}
        self._FILESCONFIG_USER[filetype] = filesconfig
    end

    -- we fetch orignal configs first if no user configs
    local fileconfig = filesconfig[sourcefile]
    if not fileconfig then
        fileconfig = table.clone(self:fileconfig(sourcefile, opt))
        filesconfig[sourcefile] = fileconfig
    end
    if fileconfig then
        for k, v in pairs(info) do
            if k == "force" then
                -- fileconfig_add("xxx.c", {force = {cxxflags = ""}})
                local force = fileconfig[k] or {}
                for k2, v2 in pairs(v) do
                    if force[k2] then
                        force[k2] = table.join(force[k2], v2)
                    else
                        force[k2] = v2
                    end
                end
                fileconfig[k] = force
            else
                -- fileconfig_add("xxx.c", {cxxflags = ""})
                if fileconfig[k] then
                    fileconfig[k] = table.join(fileconfig[k], v)
                else
                    fileconfig[k] = v
                end
            end
        end
    else
        filesconfig[sourcefile] = info
    end
end

-- get the source files
--
-- @return      the source files array
--
function _instance:sourcefiles()

    -- cached? return it directly
    if self._SOURCEFILES then
        return self._SOURCEFILES, false
    end

    -- get files
    local files = self:get("files")
    if not files then
        return {}, false
    end

    -- match files
    local i = 1
    local count = 0
    local sourcefiles = {}
    local sourcefiles_removed = {}
    local sourcefiles_inserted = {}
    local removed_count = 0
    local targetcache = memcache.cache("core.project.target")
    for _, file in ipairs(table.wrap(files)) do

        -- mark as removed files?
        local removed = false
        local prefix = "__remove_"
        if file:startswith(prefix) then
            file = file:sub(#prefix + 1)
            removed = true
        end

        local results
        if removed then
            results = {file}
        else
            -- find source files and try to cache the matching results of os.match across targets
            -- @see https://github.com/xmake-io/xmake/issues/1353
            results = targetcache:get2("sourcefiles", file)
            if results == nil then
                results = os.files(file)
                if #results == 0 then
                    -- attempt to find source directories if maybe compile it as directory with the custom rules
                    if #self:filerules(file) > 0 then
                        results = os.dirs(file)
                    end
                end

                -- Even if the current source file does not exist yet, we always add it.
                -- This is usually used for some rules that automatically generate code files,
                -- because they ensure that the code files have been generated before compilation.
                --
                -- @see https://github.com/xmake-io/xmake/issues/1540
                --
                -- e.g. add_files("src/test.c", {always_added = true})
                --
                if #results == 0 and self:extraconf("files", file, "always_added") then
                    results = {file}
                end
            end
            targetcache:set2("sourcefiles", file, results)
        end
        if #results == 0 then
            local sourceinfo = self:sourceinfo("files", file) or {}
            utils.warning("%s:%d${clear}: cannot match %s_files(\"%s\") in %s(%s)",
                sourceinfo.file or "", sourceinfo.line or -1, (removed and "remove" or "add"), file, self:type(), self:fullname())
        end

        -- process source files
        for _, sourcefile in ipairs(results) do

            -- convert to the relative path
            if path.is_absolute(sourcefile) then
                sourcefile = path.relative(sourcefile, os.projectdir())
            end

            -- add or remove it
            if removed then
                removed_count = removed_count + 1
                table.insert(sourcefiles_removed, sourcefile)
            elseif not sourcefiles_inserted[sourcefile] then
                table.insert(sourcefiles, sourcefile)
                sourcefiles_inserted[sourcefile] = true
            end
        end
    end

    -- remove all source files which need be removed
    if removed_count > 0 then
        table.remove_if(sourcefiles, function (i, sourcefile)
            for _, removed_file in ipairs(sourcefiles_removed) do
                local pattern = path.translate((removed_file:gsub("|.*$", "")))
                if pattern:sub(1, 2):find('%.[/\\]') then
                    pattern = pattern:sub(3)
                end
                pattern = path.pattern(pattern)
                -- we need to match whole pattern, https://github.com/xmake-io/xmake/issues/3523
                if sourcefile:match("^" .. pattern .. "$") then
                    return true
                end
            end
        end)
    end
    self._SOURCEFILES = sourcefiles

    -- ok and sourcefiles are modified
    return sourcefiles, true
end

-- get the object file path from source file
--
-- @param sourcefile  the source file path
-- @return          the object file path
--
function _instance:objectfile(sourcefile)
    return self:autogenfile(sourcefile, {rootdir = self:objectdir(),
        filename = target.filename(path.filename(sourcefile), "object", {
            plat = self:plat(),
            arch = self:arch(),
            format = self:_format("object")})})
end

-- get all object files
--
-- @return      the object files array
--
function _instance:objectfiles()

    -- get source batches
    local sourcebatches, modified = self:sourcebatches()

    -- cached? return it directly
    if self._OBJECTFILES and not modified then
        return self._OBJECTFILES
    end

    -- get object files from source batches
    local objectfiles = {}
    local batchcount = 0
    local orderkeys = table.keys(sourcebatches)
    table.sort(orderkeys) -- @note we need to guarantee the order of objectfiles for depend.is_changed() and etc.
    for _, k in ipairs(orderkeys) do
        local sourcebatch = sourcebatches[k]
        table.join2(objectfiles, sourcebatch.objectfiles)
        batchcount = batchcount + 1
    end

    -- some object files may be repeat and appear link errors if multi-batches exists, so we need to remove all repeat object files
    -- e.g. add_files("src/*.c", {rules = {"rule1", "rule2"}})
    local deduplicate = batchcount > 1

    -- get object files from all dependent targets (object kind)
    -- @note we only merge objects in plain deps, e.g. binary -> (static -> object, object ...)
    local plaindeps = self:get("deps")
    if plaindeps and (self:is_binary() or self:is_shared() or self:is_static()) then
        local function _get_all_objectfiles_of_object_dep (t)
            local _objectfiles = {}
            table.join2(_objectfiles, t:objectfiles())
            local _plaindeps = t:get("deps")
            if _plaindeps then
                for _, depname in ipairs(table.wrap(_plaindeps)) do
                    local dep = t:dep(depname)
                    if dep and dep:is_object() then
                        table.join2(_objectfiles, _get_all_objectfiles_of_object_dep(dep))
                    end
                end
            end
            return _objectfiles
        end
        for _, depname in ipairs(table.wrap(plaindeps)) do
            local dep = self:dep(depname)
            if dep and dep:is_object() then
                table.join2(objectfiles, _get_all_objectfiles_of_object_dep(dep))
                deduplicate = true
            end
        end
    end

    -- remove repeat object files
    if deduplicate then
        objectfiles = table.unique(objectfiles)
    end

    -- cache it
    self._OBJECTFILES = objectfiles
    return objectfiles
end

-- get the header files
function _instance:headerfiles(outputdir, opt)
    opt = opt or {}
    local headerfiles = self:get("headerfiles", opt) or {}
    -- add_headerfiles("src/*.h", {install = false})
    -- @see https://github.com/xmake-io/xmake/issues/2577
    if opt.installonly then
       local installfiles = {}
       for _, headerfile in ipairs(table.wrap(headerfiles)) do
           if self:extraconf("headerfiles", headerfile, "install") ~= false then
               table.insert(installfiles, headerfile)
           end
       end
       headerfiles = installfiles
    end
    if not headerfiles then
        return
    end

    if not outputdir then
        if self:includedir() then
            outputdir = self:includedir()
        end
    end
    return match_copyfiles(self, "headerfiles", outputdir, {copyfiles = headerfiles})
end

-- get the configuration files
function _instance:configfiles(outputdir)
    return match_copyfiles(self, "configfiles", outputdir or self:configdir(), {pathfilter = function (dstpath, fileinfo)
            if dstpath:endswith(".in") then
                dstpath = dstpath:sub(1, -4)
            end
            return dstpath
        end})
end

-- get the install files
function _instance:installfiles(outputdir, opt)
    local installfiles = self:get("installfiles", opt) or {}
    return match_copyfiles(self, "installfiles", outputdir or self:installdir(), {copyfiles = installfiles})
end

-- get the extra files
function _instance:extrafiles(outputdir)
    return match_copyfiles(self, "extrafiles", outputdir)
end

-- get depend file from object file
function _instance:dependfile(objectfile)

    -- get the dependent original file and directory, @note relative to the root directory
    local originfile = path.absolute(objectfile and objectfile or self:targetfile())
    local origindir  = path.directory(originfile)

    -- get relative directory in the object directory
    local relativedir = nil
    local objectdir = path.absolute(self:objectdir())
    if origindir:startswith(objectdir) then
        relativedir = path.relative(origindir, objectdir)
    end

    -- get relative directory in the target directory
    if not relativedir then
        local targetdir = path.absolute(self:targetdir())
        if origindir:startswith(targetdir) then
            relativedir = path.relative(origindir, targetdir)
        end
    end

    -- get relative directory in the autogen directory
    if not relativedir then
        local autogendir = path.absolute(self:autogendir())
        if origindir:startswith(autogendir) then
            relativedir = path.join("gens", path.relative(origindir, autogendir))
        end
    end

    -- get relative directory in the build directory
    if not relativedir then
        local builddir = path.absolute(config.builddir())
        if origindir:startswith(builddir) then
            relativedir = path.join("build", path.relative(origindir, builddir))
        end
    end

    -- get relative directory in the project directory
    if not relativedir then
        local projectdir = os.projectdir()
        if origindir:startswith(projectdir) then
            relativedir = path.relative(origindir, projectdir)
        end
    end

    -- get the relative directory from the origin file
    if not relativedir then
        relativedir = origindir
    end
    if path.is_absolute(relativedir) and os.host() == "windows" then
        -- remove C:\\ and whitespaces and fix long path issue
        -- e.g. C:\\Program Files (x64)\\xxx\Windows.h
        --
        -- @see
        -- https://github.com/xmake-io/xmake/issues/3021
        -- https://github.com/xmake-io/xmake/issues/3715
        relativedir = hash.strhash128(relativedir)
    end

    -- originfile: project/build/.objs/xxxx/../../xxx.c will be out of range for objectdir
    --
    -- we need to replace '..' to '__' in this case
    --
    relativedir = relativedir:gsub("%.%.", "__")

    -- make dependent file
    -- full file name(not base) to avoid name-clash of original file
    return path.join(self:dependir(), relativedir, path.filename(originfile) .. ".d")
end

-- get the dependent include files
function _instance:dependfiles()
    local sourcebatches, modified = self:sourcebatches()
    if self._DEPENDFILES and not modified then
        return self._DEPENDFILES
    end
    local dependfiles = {}
    for _, sourcebatch in pairs(self:sourcebatches()) do
        table.join2(dependfiles, sourcebatch.dependfiles)
    end
    self._DEPENDFILES = dependfiles
    return dependfiles
end

-- get the sourcekind for the given source file
function _instance:sourcekind_of(sourcefile)

    -- get the sourcekind of this source file
    local sourcekind = language.sourcekind_of(sourcefile)
    local fileconfig = self:fileconfig(sourcefile)
    if fileconfig and fileconfig.sourcekind then
        -- we can override the sourcekind, e.g. add_files("*.c", {sourcekind = "cxx"})
        sourcekind = fileconfig.sourcekind
    end
    return sourcekind
end

-- get the kinds of sourcefiles
--
-- e.g. cc cxx mm mxx as ...
--
function _instance:sourcekinds()
    local sourcekinds = self._SOURCEKINDS
    if not sourcekinds then
        sourcekinds = {}
        local sourcebatches = self:sourcebatches()
        for _, sourcebatch in table.orderpairs(sourcebatches) do
            local sourcekind = sourcebatch.sourcekind
            if sourcekind then
                table.insert(sourcekinds, sourcekind)
            end
        end
        -- if the source file is added dynamically, we may not be able to get the sourcekinds,
        -- so we can only continue to get it from the rule
        -- https://github.com/xmake-io/xmake/issues/1622#issuecomment-927726697
        for _, ruleinst in ipairs(self:orderules()) do
            local rule_sourcekinds = ruleinst:get("sourcekinds")
            if rule_sourcekinds then
                table.insert(sourcekinds, rule_sourcekinds)
            end
        end
        sourcekinds = table.unique(sourcekinds)
        self._SOURCEKINDS = sourcekinds
    end
    return sourcekinds
end

-- get source count
function _instance:sourcecount()
    return #self:sourcefiles()
end

-- get source batches grouped by source kind
--
-- @return      the source batches table {sourcekind = {sourcefiles = {...}, ...}, ...}
--
function _instance:sourcebatches()

    -- get source files
    local sourcefiles, modified = self:sourcefiles()

    -- cached? return it directly
    if self._SOURCEBATCHES and not modified then
        return self._SOURCEBATCHES, false
    end

    -- make source batches for each source kinds
    local sourcebatches = {}
    for _, sourcefile in ipairs(sourcefiles) do

        -- get file rules
        local filerules, override = self:filerules(sourcefile)
        if #filerules == 0 then
            os.raise("unknown source file: %s", sourcefile)
        end

        -- add source batch for the file rules
        for _, filerule in ipairs(filerules) do

            -- get rule name
            local rulename = filerule:name()

            -- make this batch
            local sourcebatch = sourcebatches[rulename] or {sourcefiles = {}}
            sourcebatches[rulename] = sourcebatch

            -- save the rule name
            sourcebatch.rulename = rulename

            -- add source file to this batch
            table.insert(sourcebatch.sourcefiles, sourcefile)

            -- attempt to get source kind from the builtin languages
            local sourcekind = self:sourcekind_of(sourcefile)
            if sourcekind and filerule:get("sourcekinds") and not override then

                -- save source kind
                sourcebatch.sourcekind = sourcekind

                -- insert object files to source batches
                -- and we need to avoid duplication with object files, which may cause some conflicts.
                -- e.g. c++.build, c++ module and unity_build rules
                -- @see https://github.com/xmake-io/xmake/issues/6420
                if filerule:extraconf("sourcekinds", sourcekind, "objectfiles") ~= false then
                    sourcebatch.objectfiles = sourcebatch.objectfiles or {}
                    sourcebatch.dependfiles = sourcebatch.dependfiles or {}
                    local objectfile = self:objectfile(sourcefile)
                    table.insert(sourcebatch.objectfiles, objectfile)
                    table.insert(sourcebatch.dependfiles, self:dependfile(objectfile))
                end
            end
        end
    end
    self._SOURCEBATCHES = sourcebatches
    return sourcebatches, modified
end

-- get xxx_script
function _instance:script(name, generic)

    -- get script
    local script = self:get(name)
    local result = select_script(script, {plat = self:plat(), arch = self:arch()}) or generic

    -- imports some modules first
    if result and result ~= generic then
        local scope = getfenv(result)
        if scope then
            for _, modulename in ipairs(table.wrap(self:get("imports"))) do
                scope[sandbox_module.name(modulename)] = sandbox_module.import(modulename, {anonymous = true})
            end
        end
    end
    return result
end

-- get the precompiled header file (xxx.[h|hpp|inl])
--
-- @param langkind  c/cxx
--
function _instance:pcheaderfile(langkind)
    local pcheaderfile = self:get("p" .. langkind .. "header")
    if table.empty(pcheaderfile) then
        pcheaderfile = nil
    end
    return pcheaderfile
end

-- set the precompiled header file
function _instance:pcheaderfile_set(langkind, headerfile)
    self:set("p" .. langkind .. "header", headerfile)
    self._PCOUTPUTFILES = nil
end

-- get the output of precompiled header file (xxx.h.pch)
--
-- @param langkind  c/cxx
--
function _instance:pcoutputfile(langkind)
    self._PCOUTPUTFILES = self._PCOUTPUTFILES or {}
    local pcoutputfile = self._PCOUTPUTFILES[langkind]
    if pcoutputfile then
        return pcoutputfile
    end

    -- get the precompiled header file in the object directory
    local pcheaderfile = self:pcheaderfile(langkind)
    if pcheaderfile then
        local is_gcc = false
        local is_msvc = false
        local sourcekinds = {c = "cc", cxx = "cxx", m = "mm", mxx = "mxx"}
        local sourcekind = assert(sourcekinds[langkind], "unknown language kind: " .. langkind)
        local _, toolname = self:tool(sourcekind)
        if toolname then
            if toolname == "gcc" or toolname == "gxx" then
                is_gcc = true
            elseif toolname == "cl" then
                is_msvc = true
            end
        end

        -- make precompiled output file
        --
        -- @note gcc has not -include-pch option to set the pch file path
        --
        pcoutputfile = self:objectfile(pcheaderfile)
        local pcoutputfilename = path.basename(pcoutputfile)
        if is_gcc then
            pcoutputfilename = pcoutputfilename .. ".gch"
        else
            -- different vs versions of pch files are not backward compatible,
            -- so we need to distinguish between them.
            --
            -- @see https://github.com/xmake-io/xmake/issues/5413
            local msvc = self:toolchain("msvc")
            if is_msvc and msvc then
                local vs_toolset = msvc:config("vs_toolset")
                if vs_toolset then
                    vs_toolset = sandbox_module.import("private.utils.toolchain", {anonymous = true}).get_vs_toolset_ver(vs_toolset)
                end
                if vs_toolset then
                    pcoutputfilename = pcoutputfilename .. "_" .. vs_toolset
                end
            end
            pcoutputfilename = pcoutputfilename .. ".pch"
        end
        pcoutputfile = path.join(path.directory(pcoutputfile), sourcekind, pcoutputfilename)
        self._PCOUTPUTFILES[langkind] = pcoutputfile
        return pcoutputfile
    end
end

-- get runtimes
function _instance:runtimes()
    local runtimes = self:memcache():get("runtimes")
    if runtimes == nil then
        runtimes = self:get("runtimes")
        if runtimes then
            local runtimes_supported = hashset.new()
            local toolchains = self:toolchains() or platform.load(self:plat(), self:arch()):toolchains()
            if toolchains then
                for _, toolchain_inst in ipairs(toolchains) do
                    if toolchain_inst:is_standalone() and toolchain_inst:get("runtimes") then
                        for _, runtime in ipairs(table.wrap(toolchain_inst:get("runtimes"))) do
                            runtimes_supported:insert(runtime)
                        end
                    end
                end
            end
            local runtimes_current = {}
            for _, runtime in ipairs(table.wrap(runtimes)) do
                if runtimes_supported:has(runtime) then
                    table.insert(runtimes_current, runtime)
                end
            end
            runtimes = table.unwrap(runtimes_current)
        end
        runtimes = runtimes or false
        self:memcache():set("runtimes", runtimes)
    end
    return runtimes or nil
end

-- has the given runtime for the current toolchains?
function _instance:has_runtime(...)
    local runtimes_set = self:memcache():get("runtimes_set")
    if runtimes_set == nil then
        runtimes_set = hashset.from(table.wrap(self:runtimes()))
        self:memcache():set("runtimes_set", runtimes_set)
    end
    for _, v in ipairs(table.pack(...)) do
        if runtimes_set:has(v) then
            return true
        end
    end
end

-- get the given toolchain by name
--
-- @param name  the toolchain name, e.g. "gcc", "clang", "msvc"
-- @return      the toolchain instance, or nil if not found
--
function _instance:toolchain(name)
    local toolchains_map = self:memcache():get("toolchains_map")
    if toolchains_map == nil then
        toolchains_map = {}
        for _, toolchain_inst in ipairs(self:toolchains()) do
            toolchains_map[toolchain_inst:name()] = toolchain_inst
        end
        self:memcache():set("toolchains_map", toolchains_map)
    end
    return toolchains_map[name]
end

-- get all toolchains of this target
--
-- @return      the toolchains array
--
function _instance:toolchains()
    local toolchains = self:memcache():get("toolchains")
    if toolchains == nil then

        -- load target toolchains first
        local has_standalone = false
        local target_toolchains = self:get("toolchains")
        if target_toolchains then
            toolchains = {}
            for _, name in ipairs(table.wrap(target_toolchains)) do
                local toolchain_opt = table.copy(self:extraconf("toolchains", name))
                toolchain_opt.arch = self:arch()
                toolchain_opt.plat = self:plat()
                toolchain_opt.namespace = self:namespace()
                local toolchain_inst, errors = toolchain.load(name, toolchain_opt)
                -- attempt to load toolchain from project
                if not toolchain_inst and target._project() then
                    toolchain_inst = target._project().toolchain(name, toolchain_opt)
                end
                if not toolchain_inst then
                    os.raise(errors)
                end
                if toolchain_inst:is_standalone() then
                    has_standalone = true
                end
                table.insert(toolchains, toolchain_inst)
            end

            -- we always need a standalone toolchain
            -- because we maybe only set partial toolchains in target, e.g. nasm toolchain
            --
            -- @note platform has been checked in config/_check_target_toolchains
            if not has_standalone then
                for _, toolchain_inst in ipairs(self:platform():toolchains()) do
                    if toolchain_inst:is_standalone() then
                        table.insert(toolchains, toolchain_inst)
                        has_standalone = true
                        break
                    end
                end
            end
        else
            toolchains = self:platform():toolchains()
        end

        self:memcache():set("toolchains", toolchains)
    end
    return toolchains
end

-- get the program and name of the given tool kind
--
-- @param toolkind   the tool kind, e.g. "cc", "cxx", "ld", "sh", "ar"
-- @return          the program path, the tool name
--
function _instance:tool(toolkind)
    -- we cannot get tool in on_load, because target:toolchains() has been not checked in configuration stage.
    if not self._LOADED_AFTER then
        os.raise("we cannot get tool(%s) before target(%s) is loaded, maybe it is called on_load(), please call it in on_config().", toolkind, self:fullname())
    end
    return toolchain.tool(self:toolchains(), toolkind, {cachekey = "target_" .. self:fullname(), plat = self:plat(), arch = self:arch(),
                                                        before_get = function()
        -- get program from set_toolset
        local program = self:get("toolset." .. toolkind)

        -- get program from `xmake f --cc`
        if not program and not self:get("toolchains") then
            program = config.get(toolkind)
        end

        -- contain toolname? parse it, e.g. '[email protected]'
        -- https://github.com/xmake-io/xmake/issues/1361
        local toolname
        if program then
            local pos = program:find('@', 1, true)
            if pos then
                -- we need to ignore valid path with `@`, e.g. /usr/local/opt/[email protected]/bin/go
                -- https://github.com/xmake-io/xmake/issues/2853
                local prefix = program:sub(1, pos - 1)
                if prefix and not prefix:find("[/\\]") then
                    toolname = prefix
                    program = program:sub(pos + 1)
                end
            end
        end

        -- find toolname
        if program and not toolname then
            local find_toolname = sandbox_module.import("lib.detect.find_toolname", {anonymous = true})
            toolname = find_toolname(program)
        end
        return program, toolname
    end})
end

-- get tool configuration from the toolchains
function _instance:toolconfig(name)
    return toolchain.toolconfig(self:toolchains(), name, {cachekey = "target_" .. self:fullname(), plat = self:plat(), arch = self:arch(),
                                                          after_get = function(toolchain_inst)
        -- get flags from target.on_xxflags()
        local script = toolchain_inst:get("target.on_" .. name)
        if type(script) == "function" then
            local ok, result_or_errors = utils.trycall(script, nil, self)
            if ok then
                return result_or_errors
            else
                os.raise(result_or_errors)
            end
        end
    end})
end

-- has source files with the given source kind?
function _instance:has_sourcekind(...)
    local sourcekinds_set = self._SOURCEKINDS_SET
    if sourcekinds_set == nil then
        sourcekinds_set = hashset.from(self:sourcekinds())
        self._SOURCEKINDS_SET = sourcekinds_set
    end
    for _, v in ipairs(table.pack(...)) do
        if sourcekinds_set:has(v) then
            return true
        end
    end
end

-- has the given tool for the current target?
--
-- e.g.
--
-- if target:has_tool("cc", "clang", "gcc") then
--    ...
-- end
function _instance:has_tool(toolkind, ...)
    local target_utils = target._target_utils
    if target_utils == nil then
        target_utils = sandbox_module.import("private.utils.target", {anonymous = true})
        target._target_utils = target_utils
    end
    local _, toolname = self:tool(toolkind)
    return target_utils.has_tool(toolname, table.pack(...))
end

-- has the given c funcs?
--
-- @param funcs     the funcs
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:has_cfuncs(funcs, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.has_cfuncs", {anonymous = true})(funcs, opt)
end

-- has the given c++ funcs?
--
-- @param funcs     the funcs
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:has_cxxfuncs(funcs, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.has_cxxfuncs", {anonymous = true})(funcs, opt)
end

-- has the given c types?
--
-- @param types     the types
-- @param opt       the argument options, e.g. {configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:has_ctypes(types, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.has_ctypes", {anonymous = true})(types, opt)
end

-- has the given c++ types?
--
-- @param types     the types
-- @param opt       the argument options, e.g. {configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:has_cxxtypes(types, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.has_cxxtypes", {anonymous = true})(types, opt)
end

-- has the given c includes?
--
-- @param includes  the includes
-- @param opt       the argument options, e.g. {configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:has_cincludes(includes, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.has_cincludes", {anonymous = true})(includes, opt)
end

-- has the given c++ includes?
--
-- @param includes  the includes
-- @param opt       the argument options, e.g. {configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:has_cxxincludes(includes, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.has_cxxincludes", {anonymous = true})(includes, opt)
end

-- has the given c flags?
--
-- @param flags     the flags
-- @param opt       the argument options, e.g. { flagskey = "xxx" }
--
-- @return          true or false, errors
--
function _instance:has_cflags(flags, opt)
    local compinst = self:compiler("cc")
    return compinst:has_flags(flags, "cflags", opt)
end

-- has the given c++ flags?
--
-- @param flags     the flags
-- @param opt       the argument options, e.g. { flagskey = "xxx" }
--
-- @return          true or false, errors
--
function _instance:has_cxxflags(flags, opt)
    local compinst = self:compiler("cxx")
    return compinst:has_flags(flags, "cxxflags", opt)
end

-- has the given features?
--
-- @param features  the features, e.g. {"c_static_assert", "cxx_constexpr"}
-- @param opt       the argument options, e.g. {flags = ""}
--
-- @return          true or false, errors
--
function _instance:has_features(features, opt)
    opt = opt or {}
    opt.target = self:_checked_target()
    return sandbox_module.import("core.tool.compiler", {anonymous = true}).has_features(features, opt)
end

-- check the size of type
--
-- @param typename  the typename
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          the type size
--
function _instance:check_sizeof(typename, opt)
    opt = opt or {}
    opt.target = self:_checked_target()
    return sandbox_module.import("lib.detect.check_sizeof", {anonymous = true})(typename, opt)
end

-- check the endianness of compiler
--
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          the type size
--
function _instance:check_bigendian(opt)
  opt = opt or {}
  opt.target = self:_checked_target()
  return sandbox_module.import("lib.detect.check_bigendian", {anonymous = true})(opt)
end

-- check the given c snippets?
--
-- @param snippets  the snippets
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:check_csnippets(snippets, opt)
    opt = opt or {}
    opt.target = self:_checked_target()
    return sandbox_module.import("lib.detect.check_csnippets", {anonymous = true})(snippets, opt)
end

-- check the given c++ snippets?
--
-- @param snippets  the snippets
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:check_cxxsnippets(snippets, opt)
    opt = opt or {}
    opt.target = self
    return sandbox_module.import("lib.detect.check_cxxsnippets", {anonymous = true})(snippets, opt)
end

-- check the given objc snippets?
--
-- @param snippets  the snippets
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:check_msnippets(snippets, opt)
    opt = opt or {}
    opt.target = self:_checked_target()
    return sandbox_module.import("lib.detect.check_msnippets", {anonymous = true})(snippets, opt)
end

-- check the given objc++ snippets?
--
-- @param snippets  the snippets
-- @param opt       the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}}
--
-- @return          true or false, errors
--
function _instance:check_mxxsnippets(snippets, opt)
    opt = opt or {}
    opt.target = self:_checked_target()
    return sandbox_module.import("lib.detect.check_mxxsnippets", {anonymous = true})(snippets, opt)
end

-- get project
function target._project()
    return target._PROJECT
end

-- get target apis
function target.apis()

    return
    {
        values =
        {
            -- target.set_xxx
            "target.set_kind"
        ,   "target.set_plat"
        ,   "target.set_arch"
        ,   "target.set_strip"
        ,   "target.set_rules"
        ,   "target.set_group"
        ,   "target.add_filegroups"
        ,   "target.set_version"
        ,   "target.set_license"
        ,   "target.set_enabled"
        ,   "target.set_default"
        ,   "target.set_options"
        ,   "target.set_symbols"
        ,   "target.set_filename"
        ,   "target.set_basename"
        ,   "target.set_extension"
        ,   "target.set_prefixname"
        ,   "target.set_suffixname"
        ,   "target.set_warnings"
        ,   "target.set_fpmodels"
        ,   "target.set_optimize"
        ,   "target.set_runtimes"
        ,   "target.set_languages"
        ,   "target.set_toolchains"
        ,   "target.set_runargs"
        ,   "target.set_exceptions"
        ,   "target.set_encodings"
        ,   "target.set_prefixdir"
            -- target.add_xxx
        ,   "target.add_deps"
        ,   "target.add_rules"
        ,   "target.add_options"
        ,   "target.add_packages"
        ,   "target.add_imports"
        ,   "target.add_languages"
        ,   "target.add_vectorexts"
        ,   "target.add_toolchains"
        ,   "target.add_tests"
        }
    ,   keyvalues =
        {
            -- target.set_xxx
            "target.set_values"
        ,   "target.set_configvar"
        ,   "target.set_runenv"
        ,   "target.set_toolset"
        ,   "target.set_policy"
            -- target.add_xxx
        ,   "target.add_values"
        ,   "target.add_runenvs"
        }
    ,   paths =
        {
            -- target.set_xxx
            "target.set_targetdir"
        ,   "target.set_objectdir"
        ,   "target.set_dependir"
        ,   "target.set_autogendir"
        ,   "target.set_configdir"
        ,   "target.set_installdir"
        ,   "target.set_rundir"
            -- target.add_xxx
        ,   "target.add_files"
        ,   "target.add_cleanfiles"
        ,   "target.add_configfiles"
        ,   "target.add_installfiles"
        ,   "target.add_extrafiles"
            -- target.del_xxx (deprecated)
        ,   "target.del_files"
            -- target.remove_xxx
        ,   "target.remove_files"
        ,   "target.remove_headerfiles"
        ,   "target.remove_configfiles"
        ,   "target.remove_installfiles"
        ,   "target.remove_extrafiles"
        }
    ,   script =
        {
            -- target.on_xxx
            "target.on_run"
        ,   "target.on_test"
        ,   "target.on_load"
        ,   "target.on_config"
        ,   "target.on_prepare"
        ,   "target.on_prepare_file"
        ,   "target.on_prepare_files"
        ,   "target.on_link"
        ,   "target.on_build"
        ,   "target.on_build_file"
        ,   "target.on_build_files"
        ,   "target.on_clean"
        ,   "target.on_package"
        ,   "target.on_install"
        ,   "target.on_uninstall"
        ,   "target.on_preparecmd"
        ,   "target.on_preparecmd_file"
        ,   "target.on_preparecmd_files"
        ,   "target.on_linkcmd"
        ,   "target.on_buildcmd"
        ,   "target.on_buildcmd_file"
        ,   "target.on_buildcmd_files"
        ,   "target.on_installcmd"
        ,   "target.on_uninstallcmd"
            -- target.before_xxx
        ,   "target.before_run"
        ,   "target.before_test"
        ,   "target.before_config"
        ,   "target.before_prepare"
        ,   "target.before_prepare_file"
        ,   "target.before_prepare_files"
        ,   "target.before_link"
        ,   "target.before_build"
        ,   "target.before_build_file"
        ,   "target.before_build_files"
        ,   "target.before_clean"
        ,   "target.before_package"
        ,   "target.before_install"
        ,   "target.before_uninstall"
        ,   "target.before_preparecmd"
        ,   "target.before_preparecmd_file"
        ,   "target.before_preparecmd_files"
        ,   "target.before_linkcmd"
        ,   "target.before_buildcmd"
        ,   "target.before_buildcmd_file"
        ,   "target.before_buildcmd_files"
        ,   "target.before_installcmd"
        ,   "target.before_uninstallcmd"
            -- target.after_xxx
        ,   "target.after_run"
        ,   "target.after_test"
        ,   "target.after_load"
        ,   "target.after_config"
        ,   "target.after_prepare"
        ,   "target.after_prepare_file"
        ,   "target.after_prepare_files"
        ,   "target.after_link"
        ,   "target.after_build"
        ,   "target.after_build_file"
        ,   "target.after_build_files"
        ,   "target.after_clean"
        ,   "target.after_package"
        ,   "target.after_install"
        ,   "target.after_uninstall"
        ,   "target.after_preparecmd"
        ,   "target.after_preparecmd_file"
        ,   "target.after_preparecmd_files"
        ,   "target.after_linkcmd"
        ,   "target.after_buildcmd"
        ,   "target.after_buildcmd_file"
        ,   "target.after_buildcmd_files"
        ,   "target.after_installcmd"
        ,   "target.after_uninstallcmd"
        }
    }
end

-- get the filename from the given target name and kind
function target.filename(targetname, targetkind, opt)
    opt = opt or {}
    assert(targetname and targetkind)

    -- make filename by format
    local filename = targetname
    local format = opt.format or platform.format(targetkind, opt.plat, opt.arch) or "$(name)"
    if format then
        local splitinfo = format:split("$(name)", {plain = true, strict = true})
        local prefixname = splitinfo[1] or ""
        local suffixname = ""
        local extension = splitinfo[2] or ""
        splitinfo = extension:split('.', {plain = true, limit = 2, strict = true})
        if #splitinfo == 2 and splitinfo[1] ~= "" then
            suffixname = splitinfo[1]
            extension  = "." .. splitinfo[2]
        end
        if opt.prefixname then
            prefixname = opt.prefixname
        end
        if opt.suffixname then
            suffixname = opt.suffixname
        end
        if opt.extension then
            extension = opt.extension
        end
        filename = prefixname .. targetname .. suffixname .. extension
    end
    return filename
end

-- get the link name of the target file
function target.linkname(filename, opt)
    -- for implib/mingw, e.g. libxxx.dll.a
    opt = opt or {}
    if filename:startswith("lib") and filename:endswith(".dll.a") then
        return filename:sub(4, #filename - 6)
    end
    -- for macOS, libxxx.tbd
    if filename:startswith("lib") and filename:endswith(".tbd") then
        return filename:sub(4, #filename - 4)
    end
    local linkname, count = filename:gsub(target.filename("__pattern__", "static", {plat = opt.plat}):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1")
    if count == 0 then
        linkname, count = filename:gsub(target.filename("__pattern__", "shared", {plat = opt.plat}):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1")
    end
    -- in order to be compatible with mingw/windows library with .lib
    if count == 0 and opt.plat == "mingw" then
        linkname, count = filename:gsub(target.filename("__pattern__", "static", {plat = "windows"}):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1")
    end
    if count > 0 and linkname then
        return linkname
    end
    -- fallback to the generic unix library name, libxxx.a, libxxx.so, ..
    if filename:startswith("lib") then
        if filename:endswith(".a") or filename:endswith(".so") then
            return path.basename(filename:sub(4))
        end
    elseif filename:endswith(".so") or filename:endswith(".dylib") then
        -- for custom shared libraries name, xxx.so, xxx.dylib
        return filename
    end
    return nil
end

-- new a target instance
function target.new(...)
    return _instance.new(...)
end

-- return module
return target