Ta-da
This commit is contained in:
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fantomas": {
|
||||
"version": "6.0.3",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
41
.editorconfig
Normal file
41
.editorconfig
Normal file
@@ -0,0 +1,41 @@
|
||||
root=true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=crlf
|
||||
trim_trailing_whitespace=true
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
|
||||
# ReSharper properties
|
||||
resharper_xml_indent_size=2
|
||||
resharper_xml_max_line_length=100
|
||||
resharper_xml_tab_width=2
|
||||
|
||||
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.{fs,fsi}]
|
||||
fsharp_bar_before_discriminated_union_declaration=true
|
||||
fsharp_space_before_uppercase_invocation=true
|
||||
fsharp_space_before_class_constructor=true
|
||||
fsharp_space_before_member=true
|
||||
fsharp_space_before_colon=true
|
||||
fsharp_space_before_semicolon=true
|
||||
fsharp_multiline_bracket_style=aligned
|
||||
fsharp_newline_between_type_definition_and_members=true
|
||||
fsharp_align_function_signature_to_indentation=true
|
||||
fsharp_alternative_long_member_definitions=true
|
||||
fsharp_multi_line_lambda_closing_newline=true
|
||||
fsharp_experimental_keep_indent_in_branch=true
|
||||
fsharp_max_value_binding_width=80
|
||||
fsharp_max_record_width=0
|
||||
max_line_length=120
|
||||
end_of_line=lf
|
||||
|
||||
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=2
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@ bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
|
55
Http/Errno.fs
Normal file
55
Http/Errno.fs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace Http
|
||||
|
||||
/// From errno.h on macOS, https://opensource.apple.com/source/xnu/xnu-201/bsd/sys/errno.h
|
||||
type Errno =
|
||||
| EPERM = 1
|
||||
| ENOENT = 2
|
||||
| ESRCH = 3
|
||||
| EINTR = 4
|
||||
| EIO = 5
|
||||
| ENXIO = 6
|
||||
| E2BIG = 7
|
||||
| ENOEXEC = 8
|
||||
| EBADF = 9
|
||||
| ECHILD = 10
|
||||
| EDEADLK = 11
|
||||
| EINVAL = 22
|
||||
| EFBIG = 27
|
||||
| ENOSPC = 28
|
||||
| ESPIPE = 29
|
||||
| EROFS = 30
|
||||
| EMLINK = 31
|
||||
| EPIPE = 32
|
||||
| EDOM = 33
|
||||
| ERANGE = 34
|
||||
| EAGAIN = 35
|
||||
| EINPROGRESS = 36
|
||||
| EALREADY = 37
|
||||
| ENOTSOCK = 38
|
||||
| EDESTADDRREQ = 39
|
||||
| EMSGSIZE = 40
|
||||
| EPROTOTYPE = 41
|
||||
| ENOPROTOOPT = 42
|
||||
| EPROTONOSUPPORT = 43
|
||||
| ESOCKTNOSUPPORT = 44
|
||||
| ENOTSUP = 45
|
||||
| EPFNOSUPPORT = 46
|
||||
| EAFNOSUPPORT = 47
|
||||
| EADDRINUSE = 48
|
||||
| EADDRNOTAVAIL = 49
|
||||
| ENETDOWN = 50
|
||||
| ENETUNREACH = 51
|
||||
| ENETRESET = 52
|
||||
| ECONNABORTED = 53
|
||||
| ECONNRESET = 54
|
||||
| ENOBUFS = 55
|
||||
| EISCONN = 56
|
||||
| ENOTCONN = 57
|
||||
| ESHUTDOWN = 58
|
||||
| ETOOMANYREFS = 59
|
||||
| ETIMEDOUT = 60
|
||||
| ECONNREFUSED = 61
|
||||
| ELOOP = 62
|
||||
| ENAMETOOLONG = 63
|
||||
| EHOSTDOWN = 64
|
||||
| EHOSTUNREACH = 65
|
@@ -2,10 +2,19 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<PublishAot>true</PublishAot>
|
||||
<OtherFlags>$(OtherFlags) --reflectionfree --strict-indentation</OtherFlags>
|
||||
<IlcDisableReflection>true</IlcDisableReflection>
|
||||
<WarnOn>3559</WarnOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Printf.fs" />
|
||||
<Compile Include="Errno.fs" />
|
||||
<Compile Include="Syscall.fs" />
|
||||
<Compile Include="Socket.fs" />
|
||||
<Compile Include="HttpRequest.fs" />
|
||||
<Compile Include="Program.fs"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
70
Http/HttpRequest.fs
Normal file
70
Http/HttpRequest.fs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace Http
|
||||
|
||||
open System.Text
|
||||
|
||||
// HTTP/1.1 is defined in https://www.rfc-editor.org/rfc/rfc9112.html
|
||||
|
||||
type RequestLine =
|
||||
| OriginForm of method : string * host : string * target : string
|
||||
| AbsoluteForm of method : string * uri : string
|
||||
| AuthorityForm of host : string * port : int
|
||||
| AsteriskForm
|
||||
|
||||
override this.ToString () =
|
||||
match this with
|
||||
| OriginForm (method, _host, target) -> sprintf "%s %s HTTP/1.1" method target
|
||||
| AbsoluteForm (method, uri) -> sprintf "%s %s HTTP/1.1" method uri
|
||||
| AuthorityForm (host, port) -> sprintf "CONNECT %s:%d HTTP/1.1" host port
|
||||
| AsteriskForm -> "OPTIONS * HTTP/1.1"
|
||||
|
||||
member this.AddToRequest (request : ResizeArray<byte>) : unit =
|
||||
// obvious candidate to improve speed by allocating less
|
||||
this.ToString () |> Encoding.ASCII.GetBytes |> request.AddRange
|
||||
|
||||
type FieldLine =
|
||||
{
|
||||
Name : string
|
||||
Value : string
|
||||
}
|
||||
|
||||
override this.ToString () = sprintf "%s: %s" this.Name this.Value
|
||||
|
||||
member this.AddToRequest (request : ResizeArray<byte>) : unit =
|
||||
// obvious candidate to improve speed by allocating less
|
||||
this.ToString () |> Encoding.ASCII.GetBytes |> request.AddRange
|
||||
|
||||
type HttpRequest =
|
||||
{
|
||||
Request : RequestLine
|
||||
Headers : FieldLine list
|
||||
Body : byte[]
|
||||
}
|
||||
|
||||
member this.ToBytes () : byte[] =
|
||||
let builder = ResizeArray<byte> ()
|
||||
this.Request.AddToRequest builder
|
||||
builder.Add (byte '\r')
|
||||
builder.Add (byte '\n')
|
||||
|
||||
let host =
|
||||
match this.Request with
|
||||
| OriginForm (_, host, _) -> host
|
||||
| AbsoluteForm (method, uri) ->
|
||||
failwith "I can't be bothered but here we would parse the host out of the URI"
|
||||
| AuthorityForm (host, _) -> host
|
||||
| AsteriskForm -> ""
|
||||
|
||||
builder.AddRange (Encoding.ASCII.GetBytes "Host: ")
|
||||
builder.AddRange (Encoding.ASCII.GetBytes host)
|
||||
builder.Add (byte '\r')
|
||||
builder.Add (byte '\n')
|
||||
|
||||
for h in this.Headers do
|
||||
h.AddToRequest builder
|
||||
builder.Add (byte '\r')
|
||||
builder.Add (byte '\n')
|
||||
|
||||
builder.Add (byte '\r')
|
||||
builder.Add (byte '\n')
|
||||
|
||||
builder.ToArray ()
|
11
Http/Printf.fs
Normal file
11
Http/Printf.fs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Http
|
||||
|
||||
/// Random helpers for AOT-friendly printf
|
||||
[<RequireQualifiedAccess>]
|
||||
module Printfn =
|
||||
|
||||
let int (format : string) (value : int) =
|
||||
System.Console.WriteLine (format + " " + value.ToString ())
|
||||
|
||||
let inline time (format : string) (ms : int64) =
|
||||
System.Console.WriteLine (format + " " + ms.ToString () + "ms")
|
126
Http/Program.fs
126
Http/Program.fs
@@ -1,123 +1,56 @@
|
||||
#nowarn "9"
|
||||
|
||||
namespace Http
|
||||
namespace Http
|
||||
|
||||
open System
|
||||
open System.Runtime.InteropServices
|
||||
open System.Diagnostics
|
||||
open System.Net
|
||||
open System.Net.Sockets
|
||||
open Microsoft.FSharp.NativeInterop
|
||||
|
||||
type AddressFamily =
|
||||
| INET = 2
|
||||
|
||||
type SocketType =
|
||||
| STREAM = 1
|
||||
|
||||
module Syscall =
|
||||
type byteptr = nativeptr<byte>
|
||||
[<DllImport ("libc", SetLastError=true)>]
|
||||
extern int socket (int domain, int typ, int protocol)
|
||||
|
||||
[<DllImport ("libc", SetLastError=true)>]
|
||||
extern int connect (int socket, byteptr addr, uint addrLen)
|
||||
|
||||
[<DllImport ("libc", SetLastError=true)>]
|
||||
extern int close (int fd)
|
||||
|
||||
[<DllImport ("libc", SetLastError=true)>]
|
||||
extern int read (int fd, byteptr buf, uint count)
|
||||
|
||||
[<DllImport ("libc", SetLastError=true)>]
|
||||
extern int write (int fd, byteptr buf, uint count)
|
||||
|
||||
type Sock =
|
||||
private
|
||||
{
|
||||
FileDescriptor : int
|
||||
}
|
||||
|
||||
member this.Close () =
|
||||
let result = Syscall.close this.FileDescriptor
|
||||
if result < 0 then
|
||||
failwith "failed to close"
|
||||
|
||||
module Sock =
|
||||
let create (af : AddressFamily) (sock : SocketType) =
|
||||
let fd = Syscall.socket (int af, int sock, 0)
|
||||
if fd < 0 then
|
||||
failwith "failed to create"
|
||||
{
|
||||
FileDescriptor = fd
|
||||
}
|
||||
|
||||
let write (sock : Sock) (buffer : byte []) (offset : int) (count : uint) =
|
||||
use buffer = fixed buffer
|
||||
let result = Syscall.write (sock.FileDescriptor, buffer, count)
|
||||
if result < 0 then
|
||||
failwith "failed to write"
|
||||
result
|
||||
|
||||
let read (sock : Sock) (buffer : byte []) (offset : int) (count : uint) =
|
||||
use buffer = fixed buffer
|
||||
let result = Syscall.read (sock.FileDescriptor, buffer, count)
|
||||
if result < 0 then
|
||||
failwith "failed to read"
|
||||
result
|
||||
|
||||
let close (sock : Sock) =
|
||||
sock.Close ()
|
||||
|
||||
type ConnectedSocket (sock : Socket, ep : IPEndPoint) =
|
||||
do
|
||||
sock.Connect ep
|
||||
()
|
||||
interface IDisposable with
|
||||
member _.Dispose () =
|
||||
sock.Shutdown SocketShutdown.Both
|
||||
sock.Close ()
|
||||
open System.Text
|
||||
|
||||
module Program =
|
||||
[<EntryPoint>]
|
||||
let main argv =
|
||||
let sw = Stopwatch.StartNew ()
|
||||
|
||||
let ip =
|
||||
sw.Restart ()
|
||||
let dns = Dns.GetHostEntry "example.com"
|
||||
sw.Stop ()
|
||||
printfn "DNS lookup: %ims" sw.ElapsedMilliseconds
|
||||
Printfn.time "DNS lookup:" sw.ElapsedMilliseconds
|
||||
dns.AddressList.[0]
|
||||
let ep = IPEndPoint (ip, 80)
|
||||
|
||||
sw.Restart ()
|
||||
use client = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
|
||||
use sock = Sock.create AddressFamily.INET SocketType.STREAM
|
||||
sw.Stop ()
|
||||
printfn "Socket open: %ims" sw.ElapsedMilliseconds
|
||||
Printfn.time "Socket create:" sw.ElapsedMilliseconds
|
||||
|
||||
let addr = SockAddrIPv4.Create ip 80s
|
||||
sw.Restart ()
|
||||
use _ = new ConnectedSocket (client, ep)
|
||||
Sock.connect sock addr
|
||||
sw.Stop ()
|
||||
printfn "Socket connect: %ims" sw.ElapsedMilliseconds
|
||||
Printfn.time "Socket connect:" sw.ElapsedMilliseconds
|
||||
|
||||
let message =
|
||||
[
|
||||
"GET / HTTP/1.1"
|
||||
"Host: example.com"
|
||||
"Connection: close"
|
||||
""
|
||||
""
|
||||
]
|
||||
|> String.concat "\r\n"
|
||||
|> System.Text.Encoding.ASCII.GetBytes
|
||||
{
|
||||
HttpRequest.Request = RequestLine.OriginForm ("GET", "example.com", "/")
|
||||
Headers =
|
||||
[
|
||||
{
|
||||
Name = "Connection"
|
||||
Value = "close"
|
||||
}
|
||||
]
|
||||
Body = Array.Empty<_> ()
|
||||
}
|
||||
|> fun r -> r.ToBytes ()
|
||||
|
||||
do
|
||||
let mutable written = 0
|
||||
|
||||
while written < message.Length do
|
||||
sw.Restart ()
|
||||
let write = client.Send (message, written, message.Length - written, SocketFlags.None)
|
||||
let write = Sock.write sock message written (message.Length - written |> uint)
|
||||
sw.Stop ()
|
||||
printfn "Took %ims to write %i bytes (of %i total)" sw.ElapsedMilliseconds write message.Length
|
||||
Printfn.time "Write:" sw.ElapsedMilliseconds
|
||||
Printfn.int "Bytes written:" write
|
||||
written <- written + write
|
||||
|
||||
let response =
|
||||
@@ -125,13 +58,16 @@ module Program =
|
||||
let mutable total = 0
|
||||
let mutable isDone = false
|
||||
let result = ResizeArray ()
|
||||
|
||||
while not isDone do
|
||||
sw.Restart ()
|
||||
let read = client.Receive buffer
|
||||
let read = Sock.read sock buffer 0u (uint buffer.Length)
|
||||
sw.Stop ()
|
||||
printfn "Took %ims to read %i bytes" sw.ElapsedMilliseconds read
|
||||
Printfn.time "Read:" sw.ElapsedMilliseconds
|
||||
Printfn.int "Bytes read:" read
|
||||
total <- total + read
|
||||
result.AddRange buffer.[0..read-1]
|
||||
result.AddRange buffer.[0 .. read - 1]
|
||||
|
||||
if read = 0 then
|
||||
isDone <- true
|
||||
|
||||
|
98
Http/Socket.fs
Normal file
98
Http/Socket.fs
Normal file
@@ -0,0 +1,98 @@
|
||||
#nowarn "9"
|
||||
|
||||
namespace Http
|
||||
|
||||
open System
|
||||
open System.Runtime.InteropServices
|
||||
open Microsoft.FSharp.NativeInterop
|
||||
|
||||
type Sock =
|
||||
private
|
||||
{
|
||||
FileDescriptor : int
|
||||
}
|
||||
|
||||
member this.Close () =
|
||||
let result = Syscall.close this.FileDescriptor
|
||||
|
||||
if result < 0 then
|
||||
let err = Marshal.GetLastWin32Error ()
|
||||
failwithf "failed to close: %i" err
|
||||
|
||||
interface IDisposable with
|
||||
member this.Dispose () = this.Close ()
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Sock =
|
||||
let create (af : AddressFamily) (sock : SocketType) : Sock =
|
||||
let fd = Syscall.socket (AddressFamily.toInt af, SocketType.toInt sock, 0)
|
||||
|
||||
if fd < 0 then
|
||||
let err = Marshal.GetLastWin32Error ()
|
||||
failwithf "failed to create: %i" err
|
||||
|
||||
{
|
||||
FileDescriptor = fd
|
||||
}
|
||||
|
||||
let write (sock : Sock) (buffer : byte[]) (offset : int) (count : uint) =
|
||||
use buffer = fixed buffer
|
||||
let result = Syscall.write (sock.FileDescriptor, NativePtr.add buffer offset, count)
|
||||
|
||||
if result < 0 then
|
||||
let err = Marshal.GetLastWin32Error () |> enum<Errno>
|
||||
failwithf "failed to write: %O" err
|
||||
|
||||
result
|
||||
|
||||
let read (sock : Sock) (buffer : byte[]) (offset : uint) (count : uint) =
|
||||
use buffer = fixed buffer
|
||||
|
||||
let result =
|
||||
Syscall.read (sock.FileDescriptor, NativePtr.add buffer (int offset), count)
|
||||
|
||||
if result < 0 then
|
||||
let err = Marshal.GetLastWin32Error () |> enum<Errno>
|
||||
failwithf "failed to read: %O" err
|
||||
|
||||
result
|
||||
|
||||
type private ConnectionState =
|
||||
| NotStarted
|
||||
| Done
|
||||
| Interrupted
|
||||
|
||||
let connect (sock : Sock) (addr : SockAddrIPv4) =
|
||||
use addr = fixed &addr
|
||||
let mutable isDone = ConnectionState.NotStarted
|
||||
|
||||
while isDone <> ConnectionState.Done do
|
||||
let result =
|
||||
Syscall.connect (sock.FileDescriptor, addr, Marshal.SizeOf<SockAddrIPv4> () |> uint32)
|
||||
|
||||
if result = 0 then
|
||||
isDone <- ConnectionState.Done
|
||||
elif result > 0 then
|
||||
failwithf "bad result from connect: %i" result
|
||||
else
|
||||
// Error case begins!
|
||||
|
||||
let err = Marshal.GetLastWin32Error () |> enum<Errno>
|
||||
|
||||
match isDone with
|
||||
| ConnectionState.Interrupted ->
|
||||
// It's OK to get "already connected", we were previously interrupted and we can't tell
|
||||
// how far the connection had got before we retried
|
||||
if err = Errno.EISCONN then
|
||||
isDone <- ConnectionState.Done
|
||||
else
|
||||
failwithf "failed to connect: %O" err
|
||||
| _ ->
|
||||
|
||||
if err = Errno.EINTR then
|
||||
printfn "Retrying due to EINTR"
|
||||
isDone <- ConnectionState.Interrupted
|
||||
else
|
||||
failwithf "failed to connect: %O" err
|
||||
|
||||
let close (sock : Sock) = sock.Close ()
|
65
Http/Syscall.fs
Normal file
65
Http/Syscall.fs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace Http
|
||||
|
||||
open System
|
||||
open System.Buffers.Binary
|
||||
open System.Net
|
||||
open System.Runtime.InteropServices
|
||||
|
||||
type AddressFamily =
|
||||
/// aka IPv4
|
||||
| INET = 2s
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module AddressFamily =
|
||||
let toInt (af : AddressFamily) = int af
|
||||
|
||||
type SocketType =
|
||||
| STREAM = 1
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module SocketType =
|
||||
let toInt (s : SocketType) = int s
|
||||
|
||||
[<Struct ; StructLayout(LayoutKind.Sequential)>]
|
||||
type SockAddrIPv4 =
|
||||
private
|
||||
{
|
||||
SaFamily : AddressFamily
|
||||
InPort : int16
|
||||
InAddr : uint32
|
||||
Padding : int64
|
||||
}
|
||||
|
||||
static member Create (address : IPAddress) (port : int16) =
|
||||
{
|
||||
SaFamily = AddressFamily.INET
|
||||
InPort =
|
||||
if BitConverter.IsLittleEndian then
|
||||
BinaryPrimitives.ReverseEndianness port
|
||||
else
|
||||
port
|
||||
InAddr =
|
||||
// TODO: my computer is little-endian; does this need to be endian-aware?
|
||||
BitConverter.ToUInt32 (address.GetAddressBytes (), 0)
|
||||
Padding = 0
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Syscall =
|
||||
type byteptr = nativeptr<byte>
|
||||
type sockptr = nativeptr<SockAddrIPv4>
|
||||
|
||||
[<DllImport("libc", SetLastError = true)>]
|
||||
extern int socket(int domain, int typ, int protocol)
|
||||
|
||||
[<DllImport("libc", SetLastError = true)>]
|
||||
extern int connect(int socket, sockptr addr, uint addrLen)
|
||||
|
||||
[<DllImport("libc", SetLastError = true)>]
|
||||
extern int close(int fd)
|
||||
|
||||
[<DllImport("libc", SetLastError = true)>]
|
||||
extern int read(int fd, byteptr buf, uint count)
|
||||
|
||||
[<DllImport("libc", SetLastError = true)>]
|
||||
extern int write(int fd, byteptr buf, uint count)
|
60
flake.lock
generated
Normal file
60
flake.lock
generated
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1701174899,
|
||||
"narHash": "sha256-1W+FMe8mWsJKXoBc+QgKmEeRj33kTFnPq7XCjU+bfnA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "010c7296f3b19a58b206fdf7d68d75a5b0a09e9e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
98
flake.nix
Normal file
98
flake.nix
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
description = "Declarative .NET Gitea configuration";
|
||||
|
||||
inputs = {
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
projectFile = "./Gitea.Declarative/Gitea.Declarative.fsproj";
|
||||
testProjectFile = "./Gitea.Declarative.Test/Gitea.Declarative.Test.fsproj";
|
||||
pname = "gitea-repo-config";
|
||||
dotnet-sdk = pkgs.dotnet-sdk_8;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0;
|
||||
version = "0.1";
|
||||
dotnetTool = toolName: toolVersion: sha256:
|
||||
pkgs.stdenvNoCC.mkDerivation rec {
|
||||
name = toolName;
|
||||
version = toolVersion;
|
||||
nativeBuildInputs = [pkgs.makeWrapper];
|
||||
src = pkgs.fetchNuGet {
|
||||
pname = name;
|
||||
version = version;
|
||||
sha256 = sha256;
|
||||
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
|
||||
};
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/lib"
|
||||
cp -r ./bin/* "$out/lib"
|
||||
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${name}.dll"
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in {
|
||||
packages = {
|
||||
fantomas = dotnetTool "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version "sha256-zYSF53RPbGEQt1ZBcHVYqEPHrFlmI1Ty3GQPW1uxPWw=";
|
||||
fetchDeps = let
|
||||
flags = [];
|
||||
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
|
||||
in
|
||||
pkgs.writeShellScriptBin "fetch-${pname}-deps" (builtins.readFile (pkgs.substituteAll {
|
||||
src = ./nix/fetchDeps.sh;
|
||||
pname = pname;
|
||||
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})];
|
||||
projectFiles = toString (pkgs.lib.toList projectFile);
|
||||
testProjectFiles = toString (pkgs.lib.toList testProjectFile);
|
||||
rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
|
||||
packages = dotnet-sdk.packages;
|
||||
storeSrc = pkgs.srcOnly {
|
||||
src = ./.;
|
||||
pname = pname;
|
||||
version = version;
|
||||
};
|
||||
}));
|
||||
default = pkgs.buildDotnetModule {
|
||||
pname = pname;
|
||||
name = "gitea-repo-config";
|
||||
version = version;
|
||||
src = ./.;
|
||||
projectFile = projectFile;
|
||||
nugetDeps = ./nix/deps.nix;
|
||||
doCheck = true;
|
||||
dotnet-sdk = dotnet-sdk;
|
||||
dotnet-runtime = dotnet-runtime;
|
||||
};
|
||||
};
|
||||
apps = {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${self.packages.${system}.default}/bin/Gitea.Declarative";
|
||||
};
|
||||
};
|
||||
devShells = {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
(with dotnetCorePackages;
|
||||
combinePackages [
|
||||
dotnet-sdk_8
|
||||
dotnetPackages.Nuget
|
||||
])
|
||||
] ++ [pkgs.swift darwin.apple_sdk.frameworks.Foundation darwin.apple_sdk.frameworks.CryptoKit darwin.apple_sdk.frameworks.GSS pkgs.zlib pkgs.zlib.dev pkgs.openssl pkgs.icu];
|
||||
packages = [
|
||||
pkgs.alejandra
|
||||
pkgs.nodePackages.markdown-link-check
|
||||
pkgs.shellcheck
|
||||
];
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user