Compare commits

...

29 Commits

Author SHA1 Message Date
xushiwei
8882c31eb4 Merge pull request #1090 from xushiwei/cppkg
xtool/cppkg: support latest version
2025-05-04 17:40:07 +08:00
xushiwei
8455ee8226 xtool/cppkg: support latest version 2025-05-04 17:34:02 +08:00
xushiwei
3f74aded8a Merge pull request #1089 from xushiwei/cppkg
llgo cppkg: remove unused import
2025-05-04 16:44:38 +08:00
xushiwei
2e19c2013c llgo cppkg: remove unused import 2025-05-04 16:43:07 +08:00
xushiwei
1edaa2d09b Merge pull request #1088 from xushiwei/cppkg
cmd: llgo cppkg install
2025-05-04 16:06:20 +08:00
xushiwei
beee018287 cmd: llgo cppkg install 2025-05-04 16:01:58 +08:00
xushiwei
34266ea59d Merge pull request #1087 from xushiwei/cppkg
llgo.next => llgo
2025-05-04 15:39:50 +08:00
xushiwei
f26127ce98 llgo.next => llgo 2025-05-04 15:34:32 +08:00
xushiwei
ccf321d178 Merge pull request #1086 from xushiwei/cppkg
llgo.next: support build, run, cmptest
2025-05-04 15:23:40 +08:00
xushiwei
355721c47a llgo.next: support build, run, cmptest 2025-05-04 15:18:49 +08:00
xushiwei
d400663e5d Merge pull request #1085 from xushiwei/cppkg
cmd: llgo.next
2025-05-04 14:14:10 +08:00
xushiwei
2203be945a codecov: ignore llgo.next 2025-05-04 14:10:43 +08:00
xushiwei
b9a2bf4b42 fmt: ignore gop_autogen.go 2025-05-04 14:08:43 +08:00
xushiwei
cc08195cf2 cmd: llgo.next 2025-05-04 14:04:43 +08:00
xushiwei
50c40a7828 Merge pull request #1084 from xushiwei/cppkg
go mod tidy
2025-05-04 10:41:57 +08:00
xushiwei
158be3f949 go mod tidy 2025-05-04 10:28:24 +08:00
xushiwei
0f87c322ca Merge pull request #1082 from xushiwei/cppkg
xtool/cppkg: Main => Install
2025-05-04 00:15:26 +08:00
xushiwei
d5dd19b64c xtool/cppkg: Main => Install 2025-05-04 00:08:20 +08:00
xushiwei
3032d730b7 Merge pull request #1079 from xushiwei/q
package: xtool/cppkg
2025-05-03 23:22:55 +08:00
xushiwei
e93e7126b6 package: xtool/cppkg 2025-05-03 23:13:10 +08:00
xushiwei
9bcf41d28f Merge pull request #1078 from xushiwei/q
github api: release, tag, commit
2025-05-03 22:58:01 +08:00
xushiwei
604ce47d5e github api: release, tag, commit 2025-05-03 22:50:04 +08:00
xushiwei
e1ebe150d4 Merge pull request #1077 from goplus/dependabot/go_modules/github.com/goplus/gogen-1.17.3
build(deps): bump github.com/goplus/gogen from 1.17.2 to 1.17.3
2025-04-28 08:28:56 +08:00
dependabot[bot]
ae992737e8 build(deps): bump github.com/goplus/gogen from 1.17.2 to 1.17.3
Bumps [github.com/goplus/gogen](https://github.com/goplus/gogen) from 1.17.2 to 1.17.3.
- [Release notes](https://github.com/goplus/gogen/releases)
- [Commits](https://github.com/goplus/gogen/compare/v1.17.2...v1.17.3)

---
updated-dependencies:
- dependency-name: github.com/goplus/gogen
  dependency-version: 1.17.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 00:15:49 +00:00
xushiwei
c59d609eb8 Merge pull request #1076 from xushiwei/t
_cmptest: mathbigdemo
2025-04-28 01:18:10 +08:00
xushiwei
9f26d12a3e _cmptest: mathbigdemo 2025-04-28 01:03:13 +08:00
xushiwei
9102577eba Merge pull request #1075 from xushiwei/t
go/parser demo
2025-04-28 00:46:00 +08:00
xushiwei
f0fcfde22b README: go/parser 2025-04-28 00:37:11 +08:00
xushiwei
d9d813db56 go/parser demo 2025-04-28 00:35:45 +08:00
31 changed files with 1710 additions and 10 deletions

4
.github/codecov.yml vendored
View File

@@ -1,9 +1,11 @@
coverage:
ignore:
- "chore"
- "cmd/internal"
- "cmd"
- "internal/build"
- "internal/llgen"
- "internal/mockable"
- "internal/packages"
- "internal/typepatch"
- "internal/github"
- "xtool/cppkg"

View File

@@ -21,7 +21,7 @@ jobs:
run: |
for dir in . runtime; do
pushd $dir
if [ -n "$(go fmt ./...)" ]; then
if [ -n "$(go fmt ./... | grep -v gop_autogen.go)" ]; then
echo "Some files are not properly formatted. Please run 'go fmt ./...'"
exit 1
fi

View File

@@ -335,6 +335,7 @@ Here are the Go packages that can be imported correctly:
* [regexp/syntax](https://pkg.go.dev/regexp/syntax)
* [go/token](https://pkg.go.dev/go/token)
* [go/scanner](https://pkg.go.dev/go/scanner)
* [go/parser](https://pkg.go.dev/go/parser)
## Dependencies

View File

@@ -0,0 +1,33 @@
package main
import (
"fmt"
"go/constant"
"go/token"
)
func main() {
// Create the complex number 2.3 + 5i.
ar := constant.MakeFloat64(2.3)
ai := constant.MakeImag(constant.MakeInt64(5))
a := constant.BinaryOp(ar, token.ADD, ai)
// Compute (2.3 + 5i) * 11.
b := constant.MakeUint64(11)
c := constant.BinaryOp(a, token.MUL, b)
// Convert c into a complex128.
Ar, exact := constant.Float64Val(constant.Real(c))
if !exact {
fmt.Printf("Could not represent real part %s exactly as float64\n", constant.Real(c))
}
Ai, exact := constant.Float64Val(constant.Imag(c))
if !exact {
fmt.Printf("Could not represent imaginary part %s as exactly as float64\n", constant.Imag(c))
}
C := complex(Ar, Ai)
fmt.Println("literal", 25.3+55i)
fmt.Println("go/constant", c)
fmt.Println("complex128", C)
}

View File

@@ -0,0 +1,25 @@
package main
import (
"fmt"
"math/big"
)
func main() {
// Initialize two big ints with the first two numbers in the sequence.
a := big.NewInt(0)
b := big.NewInt(1)
// Initialize limit as 10^99, the smallest integer with 100 digits.
var limit big.Int
limit.Exp(big.NewInt(10), big.NewInt(99), nil)
// Loop while a is smaller than 1e100.
for a.Cmp(&limit) < 0 {
// Compute the next Fibonacci number, storing it in a.
a.Add(a, b)
// Swap a and b so that b is the next number in the sequence.
a, b = b, a
}
fmt.Println(a) // 100-digit Fibonacci number
}

26
cmd/llgo/build_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/cmd/internal/build"
)
short "Compile packages and dependencies"
flagOff
run args => {
self.Cmd.Run self.Cmd, args
}

26
cmd/llgo/clean_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/cmd/internal/clean"
)
short "Remove object files and cached files"
flagOff
run args => {
self.Cmd.Run self.Cmd, args
}

26
cmd/llgo/cmptest_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/cmd/internal/run"
)
short "Compile and run with llgo, compare result (stdout/stderr/exitcode) with go or llgo.expect; generate llgo.expect file if -gen is specified"
flagOff
run args => {
self.CmpTestCmd.Run self.CmpTestCmd, args
}

20
cmd/llgo/cppkg_cmd.gox Normal file
View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
short "Manage C/C++ packages"
run => {
help
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/xtool/cppkg"
)
short "Install a C/C++ package from github.com/goplus/cppkg"
long `Installs a C/C++ package with the given name and version. For example:
llgo cppkg install davegamble/cjson@1.7.18
llgo cppkg install davegamble/cjson@latest
llgo cppkg install davegamble/cjson
`
run args => {
if args.len < 1 {
help
return
}
self.install args[0], self.DefaultFlags
}

20
cmd/llgo/get_cmd.gox Normal file
View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
short "Add dependencies to current module and install them"
run args => {
panic "todo"
}

247
cmd/llgo/gop_autogen.go Normal file
View File

@@ -0,0 +1,247 @@
// Code generated by gop (Go+); DO NOT EDIT.
package main
import (
"fmt"
"github.com/goplus/cobra/xcmd"
build1 "github.com/goplus/llgo/cmd/internal/build"
clean1 "github.com/goplus/llgo/cmd/internal/clean"
install1 "github.com/goplus/llgo/cmd/internal/install"
run1 "github.com/goplus/llgo/cmd/internal/run"
test1 "github.com/goplus/llgo/cmd/internal/test"
"github.com/goplus/llgo/internal/env"
cppkg1 "github.com/goplus/llgo/xtool/cppkg"
"github.com/qiniu/x/stringutil"
"runtime"
)
const _ = true
type build struct {
xcmd.Command
*App
}
type clean struct {
xcmd.Command
*App
}
type cmptest struct {
xcmd.Command
*App
}
type cppkg struct {
xcmd.Command
*App
}
type cppkg_install struct {
xcmd.Command
*App
}
type get struct {
xcmd.Command
*App
}
type install struct {
xcmd.Command
*App
}
type run struct {
xcmd.Command
*App
}
type test struct {
xcmd.Command
*App
}
type version struct {
xcmd.Command
*App
}
type App struct {
xcmd.App
}
func (this *App) Main() {
_gop_obj0 := &build{App: this}
_gop_obj1 := &clean{App: this}
_gop_obj2 := &cmptest{App: this}
_gop_obj3 := &cppkg{App: this}
_gop_obj4 := &cppkg_install{App: this}
_gop_obj5 := &get{App: this}
_gop_obj6 := &install{App: this}
_gop_obj7 := &run{App: this}
_gop_obj8 := &test{App: this}
_gop_obj9 := &version{App: this}
xcmd.Gopt_App_Main(this, _gop_obj0, _gop_obj1, _gop_obj2, _gop_obj3, _gop_obj4, _gop_obj5, _gop_obj6, _gop_obj7, _gop_obj8, _gop_obj9)
}
//line cmd/llgo/build_cmd.gox:20
func (this *build) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/build_cmd.gox:20:1
this.Short("Compile packages and dependencies")
//line cmd/llgo/build_cmd.gox:22:1
this.FlagOff()
//line cmd/llgo/build_cmd.gox:24:1
this.Run__1(func(args []string) {
//line cmd/llgo/build_cmd.gox:25:1
build1.Cmd.Run(build1.Cmd, args)
})
}
func (this *build) Classfname() string {
return "build"
}
//line cmd/llgo/clean_cmd.gox:20
func (this *clean) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/clean_cmd.gox:20:1
this.Short("Remove object files and cached files")
//line cmd/llgo/clean_cmd.gox:22:1
this.FlagOff()
//line cmd/llgo/clean_cmd.gox:24:1
this.Run__1(func(args []string) {
//line cmd/llgo/clean_cmd.gox:25:1
clean1.Cmd.Run(clean1.Cmd, args)
})
}
func (this *clean) Classfname() string {
return "clean"
}
//line cmd/llgo/cmptest_cmd.gox:20
func (this *cmptest) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/cmptest_cmd.gox:20:1
this.Short("Compile and run with llgo, compare result (stdout/stderr/exitcode) with go or llgo.expect; generate llgo.expect file if -gen is specified")
//line cmd/llgo/cmptest_cmd.gox:22:1
this.FlagOff()
//line cmd/llgo/cmptest_cmd.gox:24:1
this.Run__1(func(args []string) {
//line cmd/llgo/cmptest_cmd.gox:25:1
run1.CmpTestCmd.Run(run1.CmpTestCmd, args)
})
}
func (this *cmptest) Classfname() string {
return "cmptest"
}
//line cmd/llgo/cppkg_cmd.gox:16
func (this *cppkg) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/cppkg_cmd.gox:16:1
this.Short("Manage C/C++ packages")
//line cmd/llgo/cppkg_cmd.gox:18:1
this.Run__0(func() {
//line cmd/llgo/cppkg_cmd.gox:19:1
this.Help()
})
}
func (this *cppkg) Classfname() string {
return "cppkg"
}
//line cmd/llgo/cppkg_install_cmd.gox:20
func (this *cppkg_install) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/cppkg_install_cmd.gox:20:1
this.Short("Install a C/C++ package from github.com/goplus/cppkg")
//line cmd/llgo/cppkg_install_cmd.gox:22:1
this.Long(`Installs a C/C++ package with the given name and version. For example:
llgo cppkg install davegamble/cjson@1.7.18
llgo cppkg install davegamble/cjson@latest
llgo cppkg install davegamble/cjson
`)
//line cmd/llgo/cppkg_install_cmd.gox:29:1
this.Run__1(func(args []string) {
//line cmd/llgo/cppkg_install_cmd.gox:30:1
if len(args) < 1 {
//line cmd/llgo/cppkg_install_cmd.gox:31:1
this.Help()
//line cmd/llgo/cppkg_install_cmd.gox:32:1
return
}
//line cmd/llgo/cppkg_install_cmd.gox:35:1
cppkg1.Install(args[0], cppkg1.DefaultFlags)
})
}
func (this *cppkg_install) Classfname() string {
return "cppkg_install"
}
//line cmd/llgo/get_cmd.gox:16
func (this *get) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/get_cmd.gox:16:1
this.Short("Add dependencies to current module and install them")
//line cmd/llgo/get_cmd.gox:18:1
this.Run__1(func(args []string) {
//line cmd/llgo/get_cmd.gox:19:1
panic("todo")
})
}
func (this *get) Classfname() string {
return "get"
}
//line cmd/llgo/install_cmd.gox:20
func (this *install) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/install_cmd.gox:20:1
this.Short("Compile and install packages and dependencies")
//line cmd/llgo/install_cmd.gox:22:1
this.FlagOff()
//line cmd/llgo/install_cmd.gox:24:1
this.Run__1(func(args []string) {
//line cmd/llgo/install_cmd.gox:25:1
install1.Cmd.Run(install1.Cmd, args)
})
}
func (this *install) Classfname() string {
return "install"
}
//line cmd/llgo/run_cmd.gox:20
func (this *run) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/run_cmd.gox:20:1
this.Short("Compile and run Go program")
//line cmd/llgo/run_cmd.gox:22:1
this.FlagOff()
//line cmd/llgo/run_cmd.gox:24:1
this.Run__1(func(args []string) {
//line cmd/llgo/run_cmd.gox:25:1
run1.Cmd.Run(run1.Cmd, args)
})
}
func (this *run) Classfname() string {
return "run"
}
//line cmd/llgo/test_cmd.gox:20
func (this *test) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/test_cmd.gox:20:1
this.Short("Compile and run Go test")
//line cmd/llgo/test_cmd.gox:22:1
this.FlagOff()
//line cmd/llgo/test_cmd.gox:24:1
this.Run__1(func(args []string) {
//line cmd/llgo/test_cmd.gox:25:1
test1.Cmd.Run(test1.Cmd, args)
})
}
func (this *test) Classfname() string {
return "test"
}
//line cmd/llgo/version_cmd.gox:22
func (this *version) Main(_gop_arg0 string) {
this.Command.Main(_gop_arg0)
//line cmd/llgo/version_cmd.gox:22:1
this.Short("Print LLGo version")
//line cmd/llgo/version_cmd.gox:24:1
this.Run__0(func() {
//line cmd/llgo/version_cmd.gox:25:1
fmt.Println(stringutil.Concat("llgo ", env.Version(), " ", runtime.GOOS, "/", runtime.GOARCH))
})
}
func (this *version) Classfname() string {
return "version"
}
func main() {
//line cmd/llgo/version_cmd.gox:24:1
new(App).Main()
}

26
cmd/llgo/install_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/cmd/internal/install"
)
short "Compile and install packages and dependencies"
flagOff
run args => {
self.Cmd.Run self.Cmd, args
}

26
cmd/llgo/run_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/cmd/internal/run"
)
short "Compile and run Go program"
flagOff
run args => {
self.Cmd.Run self.Cmd, args
}

26
cmd/llgo/test_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
self "github.com/goplus/llgo/cmd/internal/test"
)
short "Compile and run Go test"
flagOff
run args => {
self.Cmd.Run self.Cmd, args
}

26
cmd/llgo/version_cmd.gox Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
import (
"runtime"
"github.com/goplus/llgo/internal/env"
)
short "Print LLGo version"
run => {
echo "llgo ${env.version} ${runtime.GOOS}/${runtime.GOARCH}"
}

12
go.mod
View File

@@ -5,18 +5,18 @@ go 1.22.0
toolchain go1.24.1
require (
github.com/goplus/gogen v1.17.2
github.com/goccy/go-yaml v1.17.1
github.com/goplus/cobra v1.9.8 //gop:class
github.com/goplus/gogen v1.17.3
github.com/goplus/lib v0.2.0
github.com/goplus/llgo/runtime v0.0.0-20250403035532-0a8a4eb6a653
github.com/goplus/llgo/runtime v0.0.0-00010101000000-000000000000
github.com/goplus/llvm v0.8.3
github.com/goplus/mod v0.16.0
github.com/qiniu/x v1.13.19
golang.org/x/mod v0.23.0
golang.org/x/tools v0.30.0
)
require (
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect
)
require golang.org/x/sync v0.11.0 // indirect
replace github.com/goplus/llgo/runtime => ./runtime

8
go.sum
View File

@@ -1,7 +1,11 @@
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/goplus/gogen v1.17.2 h1:oQDWiVzZmDAdgNieJnrvrWTmxDTqYXGFMBa2rAbwjmA=
github.com/goplus/gogen v1.17.2/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/cobra v1.9.8 h1:4ZvhxUepT35vreZdxjl/bwnJdwhWyWCGnc60+v22x14=
github.com/goplus/cobra v1.9.8/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E=
github.com/goplus/gogen v1.17.3 h1:Xhoj2KQw4feRdPEtOYjTUe9lSvNIoxBG4urhdjf+fUg=
github.com/goplus/gogen v1.17.3/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/llvm v0.8.3 h1:is1zOwhiQZWtLnOmSMVPO+1sPa2uK/XJ/FjTSfIjGBU=

76
internal/github/commit.go Normal file
View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package github
import (
"encoding/json"
"net/http"
"strings"
)
// Author represents a github user or bot.
type Author struct {
Login string `json:"login"` // github-actions[bot]
ID int `json:"id"` // 41898282
NodeID string `json:"node_id"` // MDM6Qm90NDE4OTgyODI=
AvatarURL string `json:"avatar_url"` // https://avatars.githubusercontent.com/in/15368?v=4
URL string `json:"url"` // https://api.github.com/users/github-actions%5Bbot%5D
HtmlURL string `json:"html_url"` // https://github.com/apps/github-actions
Type string `json:"type"` // Bot
SiteAdmin bool `json:"site_admin"` // false
}
// CommitAuthor represents the author of a GitHub commit.
type CommitAuthor struct {
Name string `json:"name"` // xushiwei
Email string `json:"email"` // x@goplus.org
Date string `json:"date"` // 2025-04-21T14:13:29Z
}
// CommitSummary represents the summary of a GitHub commit.
type CommitSummary struct {
Author CommitAuthor `json:"author"`
Message string `json:"message"` // Merge pull request #2296 from goplus/main\n\nv1.4.0
}
// CommitDetail represents the details of a GitHub commit.
type CommitDetail struct {
NodeID string `json:"node_id"` // C_kwDOAtpGOtoAKDE2OGEwODlmOWY5ZTNhNDdhMTliMTRjZDczODQ4N2M2ZTJkMTMxYmE
Commit CommitSummary `json:"commit"`
Author Author `json:"author"`
}
func commitURL(pkgPath, sha string) string {
return "https://api.github.com/repos/" + pkgPath + "/commits/" + sha
}
// GetCommit retrieves the details of a specific commit from a GitHub repository.
func GetCommit(pkgPath, shaOrURL string) (ret *CommitDetail, err error) {
url := shaOrURL
if !strings.HasPrefix(shaOrURL, "https://") {
url = commitURL(pkgPath, shaOrURL)
}
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
ret = new(CommitDetail)
err = json.NewDecoder(resp.Body).Decode(ret)
return
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package github
import (
"encoding/json"
"net/http"
)
// ReleaseAsset represents a GitHub release asset.
type ReleaseAsset struct {
URL string `json:"url"` // https://api.github.com/repos/flintlib/flint/releases/assets/242245930
ID int `json:"id"` // 242245930
NodeID string `json:"node_id"` // RA_kwDOAC8YHs4OcGEq
Name string `json:"name"` // flint-3.2.2.tar.gz
ContentType string `json:"content_type"` // application/x-gtar
State string `json:"state"` // uploaded
Size int64 `json:"size"` // 123456
DownloadCount int `json:"download_count"` // 176
UpdatedAt string `json:"updated_at"` // 2025-03-31T08:54:16Z
BrowserDownloadURL string `json:"browser_download_url"` // https://github.com/flintlib/flint/releases/download/v3.2.2/flint-3.2.2.tar.gz
}
// Release represents a GitHub release.
type Release struct {
URL string `json:"url"` // https://api.github.com/repos/flintlib/flint/releases/209285187
ID int `json:"id"` // 209285187
NodeID string `json:"node_id"` // RE_kwDOAC8YHs4MeXBD
TagName string `json:"tag_name"` // v3.2.2
TargetCommitish string `json:"target_commitish"` // b8223680e38ad048355a421bf7f617bb6c5d5e12
Name string `json:"name"` // FLINT v3.2.2
PublishedAt string `json:"published_at"` // 2025-03-31T08:54:16Z
Body string `json:"body"` // Release Notes
TarballURL string `json:"tarball_url"` // https://api.github.com/repos/flintlib/flint/tarball/v3.2.2
ZipballURL string `json:"zipball_url"` // https://api.github.com/repos/flintlib/flint/zipball/v3.2.2
Author Author `json:"author"`
Assets []*ReleaseAsset `json:"assets"`
Prerelease bool `json:"prerelease"`
}
// releaseURL constructs the URL for a GitHub release.
func releaseURL(pkgPath, ver string) string {
if ver == "" || ver == "latest" {
return "https://api.github.com/repos/" + pkgPath + "/releases/latest"
}
return "https://api.github.com/repos/" + pkgPath + "/releases/tags/" + ver
}
// GetRelease fetches the release information from GitHub.
func GetRelease(pkgPath, ver string) (ret *Release, err error) {
url := releaseURL(pkgPath, ver)
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
ret = new(Release)
err = json.NewDecoder(resp.Body).Decode(ret)
return
}

125
internal/github/tag.go Normal file
View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package github
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"strconv"
"strings"
)
var (
ErrBreak = errors.New("break")
ErrNotFound = errors.New("not found")
)
// Commit represents a commit in a GitHub repository.
type Commit struct {
SHA string `json:"sha"`
URL string `json:"url"`
}
// Tag represents a GitHub tag.
type Tag struct {
Name string `json:"name"`
ZipballURL string `json:"zipball_url"`
TarballURL string `json:"tarball_url"`
Commit Commit `json:"commit"`
NodeID string `json:"node_id"`
}
// tagsURL constructs the URL for fetching tags from a GitHub repository.
func tagsURL(pkgPath string) string {
return "https://api.github.com/repos/" + pkgPath + "/tags"
}
// GetTag retrieves a specific tag from a GitHub repository.
func GetTag(pkgPath, ver string) (tag *Tag, err error) {
err = ErrNotFound
EnumTags(pkgPath, 0, func(tags []*Tag, page, total int) error {
for _, t := range tags {
if t.Name == ver {
tag = t
err = nil
return ErrBreak
}
}
return nil
})
return
}
// EnumTags enumerates the tags of a GitHub repository.
func EnumTags(pkgPath string, page int, pager func(tags []*Tag, page, total int) error) (err error) {
total := 0
ubase := tagsURL(pkgPath)
loop:
u := ubase
if page > 0 {
vals := url.Values{"page": []string{strconv.Itoa(page + 1)}}
u += "?" + vals.Encode()
}
resp, err := http.Get(u)
if err != nil {
return
}
defer resp.Body.Close()
var tags []*Tag
err = json.NewDecoder(resp.Body).Decode(&tags)
if err != nil {
return
}
// Link: <https://api.github.com/repositories/47859258/tags?page=2>; rel="next",
// <https://api.github.com/repositories/47859258/tags?page=5>; rel="last"
if total == 0 {
const relLast = `rel="last"`
total = page + 1
link := resp.Header.Get("Link")
for _, part := range strings.Split(link, ",") {
if strings.HasSuffix(part, relLast) {
left := strings.TrimSpace(part[:len(part)-len(relLast)])
lastUrl := strings.TrimSuffix(strings.TrimPrefix(left, "<"), ">;")
if pos := strings.LastIndexByte(lastUrl, '?'); pos >= 0 {
if vals, e := url.ParseQuery(lastUrl[pos+1:]); e == nil {
if n, e := strconv.Atoi(vals.Get("page")); e == nil {
total = n
}
}
}
break
}
}
}
err = pager(tags, page, total)
if err != nil {
if err == ErrBreak {
err = nil
}
return
}
page++
if page < total {
goto loop
}
return
}

View File

@@ -0,0 +1,26 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package big
// ProbablyPrime reports whether x is probably prime,
// applying the Miller-Rabin test with n pseudorandomly chosen bases
// as well as a Baillie-PSW test.
//
// If x is prime, ProbablyPrime returns true.
// If x is chosen randomly and not prime, ProbablyPrime probably returns false.
// The probability of returning true for a randomly chosen non-prime is at most ¼ⁿ.
//
// ProbablyPrime is 100% accurate for inputs less than 2⁶⁴.
// See Menezes et al., Handbook of Applied Cryptography, 1997, pp. 145-149,
// and FIPS 186-4 Appendix F for further discussion of the error probabilities.
//
// ProbablyPrime is not suitable for judging primes that an adversary may
// have crafted to fool the test.
//
// As of Go 1.8, ProbablyPrime(0) is allowed and applies only a Baillie-PSW test.
// Before Go 1.8, ProbablyPrime applied only the Miller-Rabin tests, and ProbablyPrime(0) panicked.
func (x *Int) ProbablyPrime(n int) bool {
panic("ProbablyPrime: todo")
}

View File

@@ -0,0 +1,13 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
// Compiler is the name of the compiler toolchain that built the
// running binary. Known toolchains are:
//
// - gc Also known as cmd/compile.
// - gccgo The gccgo front end, part of the GCC compiler suite.
// - llgo Our project
const Compiler = "llgo"

88
xtool/cppkg/command.go Normal file
View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package cppkg
import (
"os"
"os/exec"
"strings"
)
var (
// ErrNotFound is the error resulting if a path search failed to find
// an executable file.
ErrNotFound = exec.ErrNotFound
)
// Tool represents a tool that can be executed.
type Tool struct {
cmd string
installs [][]string
}
// NewTool creates a new Tool instance with the specified tool and install commands.
func NewTool(cmd string, installs []string) *Tool {
inst := make([][]string, len(installs))
for i, install := range installs {
inst[i] = strings.Split(install, " ")
}
return &Tool{
cmd: cmd,
installs: inst,
}
}
// New creates a new command with the specified arguments.
func (p *Tool) New(quietInstall bool, args ...string) (cmd *exec.Cmd, err error) {
app, err := p.Get(quietInstall)
if err != nil {
return
}
return exec.Command(app, args...), nil
}
// Get retrieves the path of the command.
// If the command is not found, it attempts to install it using the specified
// install commands.
func (p *Tool) Get(quietInstall bool) (app string, err error) {
app, err = exec.LookPath(p.cmd)
if err == nil {
return
}
amPath, install, err := p.getAppManager()
if err != nil {
return
}
c := exec.Command(amPath, install[1:]...)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err = c.Run(); err != nil {
return
}
return exec.LookPath(p.cmd)
}
func (p *Tool) getAppManager() (amPath string, install []string, err error) {
for _, install = range p.installs {
am := install[0]
if amPath, err = exec.LookPath(am); err == nil {
return
}
}
err = ErrNotFound
return
}

300
xtool/cppkg/conan.go Normal file
View File

@@ -0,0 +1,300 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package cppkg
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/goccy/go-yaml"
"github.com/goplus/llgo/internal/github"
"github.com/qiniu/x/httputil"
)
var conanCmd = NewTool("conan", []string{
"brew install conan",
"apt-get install conan",
})
type conandata struct {
Sources map[string]any `yaml:"sources"`
}
func replaceVer(src any, fromVer, toVer string) any {
switch src := src.(type) {
case map[string]any:
doReplace(src, fromVer, toVer)
case []any:
for _, u := range src {
doReplace(u.(map[string]any), fromVer, toVer)
}
}
return src
}
func doReplace(src map[string]any, fromVer, toVer string) {
switch url := src["url"].(type) {
case string:
src["url"] = strings.ReplaceAll(url, fromVer, toVer)
delete(src, "sha256")
// TODO(xsw): src["sha256"] = hash
case []any:
for i, u := range url {
url[i] = strings.ReplaceAll(u.(string), fromVer, toVer)
}
delete(src, "sha256")
// TODO(xsw): src["sha256"] = hash
}
}
type githubRelease struct {
PublishedAt string
}
func getRelease(pkg *Package, tagPattern string) (ret *githubRelease, err error) {
if tagPattern == "" {
return nil, ErrDynamicTag
}
if pkg.gr != nil {
return &githubRelease{PublishedAt: pkg.gr.PublishedAt}, nil
}
ver := strings.Replace(tagPattern, "*", pkg.Version, 1)
gr, err := github.GetRelease(pkg.Path, ver)
if err == nil {
ret = &githubRelease{PublishedAt: gr.PublishedAt}
return
}
t, err := github.GetTag(pkg.Path, ver)
if err != nil {
return
}
c, err := github.GetCommit(pkg.Path, t.Commit.URL)
if err == nil {
ret = &githubRelease{PublishedAt: c.Commit.Author.Date}
}
return
}
// Install installs the specified package using Conan.
func (p *Manager) Install(pkg *Package, flags int) (err error) {
outDir := p.outDir(pkg)
os.MkdirAll(outDir, os.ModePerm)
var rev string
var gr *githubRelease
var conandataYml, conanfilePy []byte
conanfileDir := p.conanfileDir(pkg.Path, pkg.Folder)
pkgVer := pkg.Version
template := pkg.Template
if template != nil {
gr, err = getRelease(pkg, template.Tag)
if err != nil {
return
}
err = copyDirR(conanfileDir, outDir)
if err != nil {
return
}
conanfilePy, err = os.ReadFile(outDir + "/conanfile.py")
if err != nil {
return
}
conandataFile := outDir + "/conandata.yml"
conandataYml, err = os.ReadFile(conandataFile)
if err != nil {
return
}
var cd conandata
err = yaml.Unmarshal(conandataYml, &cd)
if err != nil {
return
}
fromVer := template.FromVer
source, ok := cd.Sources[fromVer]
if !ok {
return ErrVersionNotFound
}
cd.Sources = map[string]any{
pkgVer: replaceVer(source, fromVer, pkgVer),
}
conandataYml, err = yaml.Marshal(cd)
if err != nil {
return
}
err = os.WriteFile(conandataFile, conandataYml, os.ModePerm)
if err != nil {
return
}
rev = recipeRevision(pkg, gr, conandataYml)
conanfileDir = outDir
}
outFile := outDir + "/out.json"
out, err := os.Create(outFile)
if err == nil {
defer out.Close()
} else {
out = os.Stdout
}
nameAndVer := pkg.Name + "/" + pkgVer
if template == nil {
return conanInstall(nameAndVer, outDir, conanfileDir, out, flags)
}
logFile := ""
if flags&LogRevertProxy != 0 {
logFile = outDir + "/rp.log"
}
return remoteProxy(flags, logFile, func() error {
return conanInstall(nameAndVer, outDir, conanfileDir, out, flags)
}, func(mux *http.ServeMux) {
base := "/v2/conans/" + nameAndVer
revbase := base + "/_/_/revisions/" + rev
mux.HandleFunc(base+"/_/_/latest", func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
h.Set("Cache-Control", "public,max-age=300")
httputil.Reply(w, http.StatusOK, map[string]any{
"revision": rev,
"time": gr.PublishedAt,
})
})
mux.HandleFunc(revbase+"/files", func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
h.Set("Cache-Control", "public,max-age=3600")
empty := map[string]any{}
httputil.Reply(w, http.StatusOK, map[string]any{
"files": map[string]any{
"conan_export.tgz": empty,
"conanmanifest.txt": empty,
"conanfile.py": empty,
},
})
})
mux.HandleFunc(revbase+"/files/conanfile.py", func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
h.Set("Cache-Control", "public,max-age=3600")
h.Set("Content-Disposition", `attachment; filename="conanfile.py"`)
httputil.ReplyWith(w, http.StatusOK, "text/x-python", conanfilePy)
})
const conanmanifest = "%d\nconandata.yml: %s\nconanfile.py: %s\n"
mux.HandleFunc(revbase+"/files/conanmanifest.txt", func(w http.ResponseWriter, r *http.Request) {
mtime, err := unixTime(gr.PublishedAt)
if err != nil {
replyError(w, err)
return
}
h := w.Header()
h.Set("Cache-Control", "public,max-age=3600")
h.Set("Content-Disposition", `attachment; filename="conanmanifest.txt"`)
data := fmt.Sprintf(conanmanifest, mtime, md5Of(conandataYml), md5Of(conanfilePy))
httputil.ReplyWithStream(w, http.StatusOK, "text/plain", strings.NewReader(data), int64(len(data)))
})
mux.HandleFunc(revbase+"/files/conan_export.tgz", func(w http.ResponseWriter, r *http.Request) {
conanExportTgz, err := tgzOfConandata(outDir)
if err != nil {
replyError(w, err)
return
}
h := w.Header()
h.Set("Cache-Control", "public,max-age=3600")
h.Set("Content-Disposition", `attachment; filename="conan_export.tgz"`)
httputil.ReplyWith(w, http.StatusOK, "application/x-gzip", conanExportTgz)
})
})
}
func (p *Manager) outDir(pkg *Package) string {
return p.cacheDir + "/build/" + pkg.Name + "@" + pkg.Version
}
func (p *Manager) conanfileDir(pkgPath, pkgFolder string) string {
root := p.indexRoot()
return root + "/" + pkgPath + "/" + pkgFolder
}
func conanInstall(pkg, outDir, conanfileDir string, out io.Writer, flags int) (err error) {
args := make([]string, 0, 12)
args = append(args, "install",
"--requires", pkg,
"--generator", "PkgConfigDeps",
"--build", "missing",
"--format", "json",
"--output-folder", outDir,
)
quietInstall := flags&ToolQuietInstall != 0
cmd, err := conanCmd.New(quietInstall, args...)
if err != nil {
return
}
cmd.Dir = conanfileDir
cmd.Stderr = os.Stderr
cmd.Stdout = out
err = cmd.Run()
return
}
func recipeRevision(_ *Package, _ *githubRelease, conandataYml []byte) string {
return md5Of(conandataYml)
}
func md5Of(data []byte) string {
h := md5.New()
h.Write(data)
return hex.EncodeToString(h.Sum(nil))
}
func tgzOfConandata(outDir string) (_ []byte, err error) {
cmd := exec.Command("tar", "-czf", "conan_export.tgz", "conandata.yml")
cmd.Dir = outDir
err = cmd.Run()
if err != nil {
return
}
return os.ReadFile(outDir + "/conan_export.tgz")
}
func unixTime(tstr string) (ret int64, err error) {
t, err := time.Parse(time.RFC3339, tstr)
if err == nil {
ret = t.Unix()
}
return
}
func copyDirR(srcDir, destDir string) error {
if cp, err := exec.LookPath("cp"); err == nil {
return exec.Command(cp, "-r", "-p", srcDir+"/", destDir).Run()
}
if cp, err := exec.LookPath("xcopy"); err == nil {
// TODO(xsw): check xcopy
return exec.Command(cp, "/E", "/I", "/Y", srcDir+"/", destDir).Run()
}
return errors.New("copy command not found")
}

55
xtool/cppkg/cppkg.go Normal file
View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package cppkg
import (
"strings"
)
const (
// DefaultFlags is the default flags for package installation.
DefaultFlags = IndexAutoUpdate | ToolQuietInstall
)
// Install installs a package with the given name and version.
// pkgAndVer: 7bitcoder/7bitconf@1.2.0
func Install(pkgAndVer string, flags int) {
pkgPath, ver := parsePkgVer(pkgAndVer)
m, err := New("")
check(err)
pkg, err := m.Lookup(pkgPath, ver, flags)
check(err)
err = m.Install(pkg, flags)
check(err)
}
func parsePkgVer(pkg string) (string, string) {
parts := strings.SplitN(pkg, "@", 2)
if len(parts) == 1 {
return parts[0], ""
}
return parts[0], parts[1]
}
func check(err error) {
if err != nil {
panic(err)
}
}

196
xtool/cppkg/manager.go Normal file
View File

@@ -0,0 +1,196 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package cppkg
import (
"errors"
"os"
"strings"
"github.com/goccy/go-yaml"
"github.com/goplus/llgo/internal/github"
"golang.org/x/mod/semver"
)
var gitCmd = NewTool("git", []string{
"brew install git",
"apt-get install git",
})
// Manager represents a package manager for C/C++ packages.
type Manager struct {
cacheDir string
}
func New(cacheDir string) (ret *Manager, err error) {
if cacheDir == "" {
cacheDir, err = os.UserCacheDir()
if err != nil {
return
}
cacheDir += "/cppkg"
}
os.MkdirAll(cacheDir, os.ModePerm)
ret = &Manager{
cacheDir: cacheDir,
}
return
}
type version struct {
Folder string `yaml:"folder"`
}
// Template represents a template for package versions.
type Template struct {
FromVer string `yaml:"from"`
Folder string `yaml:"folder"`
Tag string `yaml:"tag,omitempty"` // pattern with *, empty if dynamic tag
}
type config struct {
PkgName string `yaml:"name"`
Versions map[string]version `yaml:"versions"`
Template Template `yaml:"template"`
}
// Package represents a C/C++ package.
type Package struct {
Name string
Path string
Version string
Folder string
Template *Template
gr *github.Release // optional
}
var (
// ErrVersionNotFound is returned when the specified version is not found.
ErrVersionNotFound = errors.New("version not found")
// ErrDynamicTag is returned when the tag is dynamic.
ErrDynamicTag = errors.New("dynamic tag")
)
const (
// IndexAutoUpdate is a flag to automatically update the index.
IndexAutoUpdate = 1 << iota
// ToolQuietInstall is a flag to suppress output during installation.
ToolQuietInstall
// LogRevertProxy is a flag to log revert proxy.
LogRevertProxy
)
// Lookup looks up a package by its path and version.
func (p *Manager) Lookup(pkgPath, ver string, flags int) (_ *Package, err error) {
root := p.indexRoot()
err = indexUpate(root, flags)
if err != nil {
return
}
pkgDir := root + "/" + pkgPath
confFile := pkgDir + "/config.yml"
b, err := os.ReadFile(confFile)
if err != nil {
return
}
var conf config
err = yaml.Unmarshal(b, &conf)
if err != nil {
return
}
if ver == "" || ver == "latest" {
if conf.Template.Tag == "" {
return nil, ErrDynamicTag
}
gr, e := github.GetRelease(pkgPath, "")
if e != nil {
return nil, e
}
ver, err = verByTag(gr.TagName, conf.Template.Tag)
if err != nil {
return
}
templ := conf.Template
return &Package{conf.PkgName, pkgPath, ver, templ.Folder, &templ, gr}, nil
}
if v, ok := conf.Versions[ver]; ok {
return &Package{conf.PkgName, pkgPath, ver, v.Folder, nil, nil}, nil
}
if compareVer(ver, conf.Template.FromVer) < 0 {
err = ErrVersionNotFound
return
}
templ := conf.Template
return &Package{conf.PkgName, pkgPath, ver, templ.Folder, &templ, nil}, nil
}
func (p *Manager) indexRoot() string {
return p.cacheDir + "/index"
}
func indexUpate(root string, flags int) (err error) {
if _, err = os.Stat(root + "/.git"); os.IsNotExist(err) {
os.RemoveAll(root)
return indexInit(root, flags)
}
if flags&IndexAutoUpdate != 0 {
quietInstall := flags&ToolQuietInstall != 0
git, e := gitCmd.New(quietInstall, "pull", "--ff-only", "origin", "main")
if e != nil {
return e
}
git.Dir = root
git.Stdout = os.Stdout
git.Stderr = os.Stderr
err = git.Run()
}
return
}
func indexInit(root string, flags int) (err error) {
quietInstall := flags&ToolQuietInstall != 0
git, err := gitCmd.New(quietInstall, "clone", "https://github.com/goplus/cppkg.git", root)
if err != nil {
return
}
git.Stdout = os.Stdout
git.Stderr = os.Stderr
err = git.Run()
return
}
func compareVer(v1, v2 string) int {
return semver.Compare("v"+v1, "v"+v2)
}
func verByTag(tag, tagPattern string) (ver string, err error) {
if pos := strings.IndexByte(tagPattern, '*'); pos >= 0 {
prefix := tagPattern[:pos]
suffix := tagPattern[pos+1:]
if strings.HasPrefix(tag, prefix) && strings.HasSuffix(tag, suffix) {
ver = tag[pos : len(tag)-len(suffix)]
return
}
}
return "", errors.New("tag not match: " + tag + " with " + tagPattern)
}

176
xtool/cppkg/revertproxy.go Normal file
View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* 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.
*/
package cppkg
import (
"bytes"
"encoding/json"
"io"
stdlog "log"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"os"
"os/exec"
"strings"
)
type rtHandler func(req *http.Request) (resp *http.Response, err error)
func (p rtHandler) RoundTrip(req *http.Request) (*http.Response, error) {
return p(req)
}
type teeReader struct {
rc io.ReadCloser
b bytes.Buffer
req *http.Request
resp *http.Response
log *stdlog.Logger
}
func (p *teeReader) Read(b []byte) (n int, err error) {
n, err = p.rc.Read(b)
p.b.Write(b[:n])
return
}
func (p *teeReader) Close() error {
err := p.rc.Close()
if log := p.log; log != nil {
resp := *p.resp
resp.Body = io.NopCloser(&p.b)
var b bytes.Buffer
p.req.Write(&b)
resp.Write(&b)
log.Print(b.String())
}
return err
}
type response = httptest.ResponseRecorder
func newResponse() *response {
return httptest.NewRecorder()
}
type revertProxy = httptest.Server
type rpFunc = func(mux *http.ServeMux)
const (
passThrough = http.StatusNotFound
)
func replyError(w http.ResponseWriter, _ error) {
w.WriteHeader(passThrough)
}
func startRevertProxy(endpoint string, f rpFunc, log *stdlog.Logger) (_ *revertProxy, err error) {
rpURL, err := url.Parse(endpoint)
if err != nil {
return
}
var mux *http.ServeMux
if f != nil {
mux = http.NewServeMux()
f(mux)
}
proxy := httptest.NewServer(&httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(rpURL)
},
Transport: rtHandler(func(req *http.Request) (resp *http.Response, err error) {
if mux != nil {
w := newResponse()
mux.ServeHTTP(w, req)
if w.Code != passThrough {
resp = w.Result()
}
}
if resp == nil {
resp, err = http.DefaultTransport.RoundTrip(req)
}
if err == nil && resp.Body != nil {
resp.Body = &teeReader{
rc: resp.Body,
req: req,
resp: resp,
log: log,
}
}
return
}),
})
return proxy, nil
}
const (
conanCenter = "conancenter"
conanEndpoint = "https://center2.conan.io"
)
type remoteList []struct {
Name string `json:"name"`
URL string `json:"url"`
}
func remoteProxy(flags int, logFile string, f func() error, rpf rpFunc) (err error) {
quietInstall := flags&ToolQuietInstall != 0
app, err := conanCmd.Get(quietInstall)
if err != nil {
return
}
endpoint := conanEndpoint
cmd := exec.Command(app, "remote", "list", "-f", "json")
if b, err := cmd.Output(); err == nil {
var rl remoteList
if json.Unmarshal(b, &rl) == nil {
for _, r := range rl {
if r.Name == conanCenter && strings.HasPrefix(r.URL, "https://") {
endpoint = r.URL
break
}
}
}
}
defer func() {
exec.Command(app, "remote", "add", "--force", conanCenter, endpoint).Run()
}()
var log *stdlog.Logger
if logFile != "" {
f, err := os.Create(logFile)
if err == nil {
defer f.Close()
log = stdlog.New(f, "", stdlog.LstdFlags)
}
}
rp, err := startRevertProxy(conanEndpoint, rpf, log)
if err != nil {
return
}
defer rp.Close()
err = exec.Command(app, "remote", "add", "--force", conanCenter, rp.URL).Run()
if err != nil {
return
}
return f()
}