From d0aa5472f75f35528dc77ee0e68203350b7c836f Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Mon, 3 Mar 2025 08:37:32 +0100 Subject: [PATCH 1/5] add OS query code --- src/stdlib_system.F90 | 197 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 2 deletions(-) diff --git a/src/stdlib_system.F90 b/src/stdlib_system.F90 index 3c7858506..0c62b088e 100644 --- a/src/stdlib_system.F90 +++ b/src/stdlib_system.F90 @@ -5,6 +5,70 @@ module stdlib_system private public :: sleep +!! version: experimental +!! +!! Cached OS type retrieval with negligible runtime overhead. +!! ([Specification](../page/specs/stdlib_system.html#os_type-cached-os-type-retrieval)) +!! +!! ### Summary +!! Provides a cached value for the runtime OS type. +!! +!! ### Description +!! +!! This function caches the result of `get_runtime_os` after the first invocation. +!! Subsequent calls return the cached value, ensuring minimal overhead. +!! +public :: OS_TYPE + +!! version: experimental +!! +!! Determine the current operating system (OS) type at runtime. +!! ([Specification](../page/specs/stdlib_system.html#get_runtime_os-determine-the-os-type-at-runtime)) +!! +!! ### Summary +!! This function inspects the runtime environment to identify the OS type. +!! +!! ### Description +!! +!! The function evaluates environment variables (`OSTYPE` or `OS`) and filesystem attributes +!! to identify the OS. It distinguishes between several common operating systems: +!! - Linux +!! - macOS +!! - Windows +!! - Cygwin +!! - Solaris +!! - FreeBSD +!! - OpenBSD +!! +!! Returns a constant representing the OS type or `OS_UNKNOWN` if the OS cannot be determined. +!! +public :: get_runtime_os + +!> Version: experimental +!> +!> Integer constants representing known operating system (OS) types +!> ([Specification](../page/specs/stdlib_system.html)) +integer, parameter, public :: & + !> Represents an unknown operating system + OS_UNKNOWN = 0, & + !> Represents a Linux operating system + OS_LINUX = 1, & + !> Represents a macOS operating system + OS_MACOS = 2, & + !> Represents a Windows operating system + OS_WINDOWS = 3, & + !> Represents a Cygwin environment + OS_CYGWIN = 4, & + !> Represents a Solaris operating system + OS_SOLARIS = 5, & + !> Represents a FreeBSD operating system + OS_FREEBSD = 6, & + !> Represents an OpenBSD operating system + OS_OPENBSD = 7 + +!! Helper function returning the name of an OS parameter +public :: OS_NAME + !> Public sub-processing interface public :: run public :: runasync @@ -218,7 +282,6 @@ module logical function process_is_running(process) result(is_running) end function process_is_running end interface is_running - interface is_completed !! version: experimental !! @@ -397,7 +460,11 @@ subroutine process_callback(pid,exit_state,stdin,stdout,stderr,payload) class(*), optional, intent(inout) :: payload end subroutine process_callback end interface - + +!! Static storage for the current OS +logical :: have_os = .false. +integer :: OS_CURRENT = OS_UNKNOWN + interface !! version: experimental @@ -430,4 +497,130 @@ end function process_get_ID end interface +contains + +integer function get_runtime_os() result(os) + !! The function identifies the OS by inspecting environment variables and filesystem attributes. + !! + !! ### Returns: + !! - **OS_UNKNOWN**: If the OS cannot be determined. + !! - **OS_LINUX**, **OS_MACOS**, **OS_WINDOWS**, **OS_CYGWIN**, **OS_SOLARIS**, **OS_FREEBSD**, or **OS_OPENBSD**. + !! + !! Note: This function performs a detailed runtime inspection, so it has non-negligible overhead. + + ! Local variables + character(len=255) :: val + integer :: length, rc + logical :: file_exists + + os = OS_UNKNOWN + + ! Check environment variable `OSTYPE`. + call get_environment_variable('OSTYPE', val, length, rc) + + if (rc == 0 .and. length > 0) then + ! Linux + if (index(val, 'linux') > 0) then + os = OS_LINUX + return + end if + + ! macOS + if (index(val, 'darwin') > 0) then + os = OS_MACOS + return + end if + + ! Windows, MSYS, MinGW, Git Bash + if (index(val, 'win') > 0 .or. index(val, 'msys') > 0) then + os = OS_WINDOWS + return + end if + + ! Cygwin + if (index(val, 'cygwin') > 0) then + os = OS_CYGWIN + return + end if + + ! Solaris, OpenIndiana, ... + if (index(val, 'SunOS') > 0 .or. index(val, 'solaris') > 0) then + os = OS_SOLARIS + return + end if + + ! FreeBSD + if (index(val, 'FreeBSD') > 0 .or. index(val, 'freebsd') > 0) then + os = OS_FREEBSD + return + end if + + ! OpenBSD + if (index(val, 'OpenBSD') > 0 .or. index(val, 'openbsd') > 0) then + os = OS_OPENBSD + return + end if + end if + + ! Check environment variable `OS`. + call get_environment_variable('OS', val, length, rc) + + if (rc == 0 .and. length > 0 .and. index(val, 'Windows_NT') > 0) then + os = OS_WINDOWS + return + end if + + ! Linux + inquire (file='/etc/os-release', exist=file_exists) + + if (file_exists) then + os = OS_LINUX + return + end if + + ! macOS + inquire (file='/usr/bin/sw_vers', exist=file_exists) + + if (file_exists) then + os = OS_MACOS + return + end if + + ! FreeBSD + inquire (file='/bin/freebsd-version', exist=file_exists) + + if (file_exists) then + os = OS_FREEBSD + return + end if +end function get_runtime_os + +!> Retrieves the cached OS type for minimal runtime overhead. +integer function OS_TYPE() result(os) + !! This function uses a static cache to avoid recalculating the OS type after the first call. + !! It is recommended for performance-sensitive use cases where the OS type is checked multiple times. + if (.not.have_os) then + OS_CURRENT = get_runtime_os() + have_os = .true. + end if + os = OS_CURRENT +end function OS_TYPE + +!> Return string describing the OS type flag +pure function OS_NAME(os) + integer, intent(in) :: os + character(len=:), allocatable :: OS_NAME + + select case (os) + case (OS_LINUX); OS_NAME = "Linux" + case (OS_MACOS); OS_NAME = "macOS" + case (OS_WINDOWS); OS_NAME = "Windows" + case (OS_CYGWIN); OS_NAME = "Cygwin" + case (OS_SOLARIS); OS_NAME = "Solaris" + case (OS_FREEBSD); OS_NAME = "FreeBSD" + case (OS_OPENBSD); OS_NAME = "OpenBSD" + case default ; OS_NAME = "Unknown" + end select +end function OS_NAME + end module stdlib_system From eaaa0d1da302c5909602ff587f58b79ded78e2d3 Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Mon, 3 Mar 2025 08:46:21 +0100 Subject: [PATCH 2/5] add docs --- doc/specs/stdlib_system.md | 106 +++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/doc/specs/stdlib_system.md b/doc/specs/stdlib_system.md index 0ab9e2e1d..879a7d51e 100644 --- a/doc/specs/stdlib_system.md +++ b/doc/specs/stdlib_system.md @@ -335,3 +335,109 @@ Returns a `logical` flag: `.true.` if the system is Windows, or `.false.` otherw ```fortran {!example/system/example_process_1.f90!} ``` + +## `get_runtime_os` - Determine the OS type at runtime + +### Status + +Experimental + +### Description + +`get_runtime_os` inspects the runtime environment to identify the current OS type. It evaluates environment variables (`OSTYPE`, `OS`) and checks for specific files associated with known operating systems. +The supported OS types are `integer, parameter` variables stored in the `stdlib_system` module: + +- **Linux** (`OS_LINUX`) +- **macOS** (`OS_MACOS`) +- **Windows** (`OS_WINDOWS`) +- **Cygwin** (`OS_CYGWIN`) +- **Solaris** (`OS_SOLARIS`) +- **FreeBSD** (`OS_FREEBSD`) +- **OpenBSD** (`OS_OPENBSD`) + +If the OS cannot be identified, the function returns `OS_UNKNOWN`. + +### Syntax + +`os = [[stdlib_system(module):get_runtime_os(function)]]()` + +### Class + +Function + +### Arguments + +None. + +### Return Value + +Returns one of the `integer` `OS_*` parameters representing the OS type, from the `stdlib_system` module, or `OS_UNKNOWN` if undetermined. + +### Example + +```fortran +program example_os_detection + use stdlib_system, only: OS_TYPE, get_runtime_os + implicit none + integer :: os_type_cached, os_type_runtime + + ! Cached OS detection + os_type_cached = OS_TYPE() + print *, "Cached OS Type: ", os_type_cached + + ! Runtime OS detection (full inspection) + os_type_runtime = get_runtime_os() + print *, "Runtime OS Type: ", os_type_runtime +end program example_os_detection +``` + +--- + +## `OS_TYPE` - Cached OS type retrieval + +### Status + +Experimental + +### Description + +`OS_TYPE` provides a cached result of the `get_runtime_os` function. The OS type is determined during the first invocation and stored in a static variable. +Subsequent calls reuse the cached value, making this function highly efficient. + +This caching mechanism ensures negligible overhead for repeated calls, unlike `get_runtime_os`, which performs a full runtime inspection. + +### Syntax + +`os = [[stdlib_system(module):OS_TYPE(function)]]()` + +### Class + +Function + +### Arguments + +None. + +### Return Value + +Returns one of the `integer` `OS_*` parameters representing the OS type, from the `stdlib_system` module, or `OS_UNKNOWN` if undetermined. + +--- + +### Example + +```fortran +program example_os_detection + use stdlib_system, only: OS_TYPE, get_runtime_os + implicit none + integer :: os_type_cached, os_type_runtime + + ! Cached OS detection + os_type_cached = OS_TYPE() + print *, "Cached OS Type: ", os_type_cached + + ! Runtime OS detection (full inspection) + os_type_runtime = get_runtime_os() + print *, "Runtime OS Type: ", os_type_runtime +end program example_os_detection +``` From 2596c8c988bf5599ae9f92527550fa5fddd0a0c1 Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Mon, 3 Mar 2025 08:52:26 +0100 Subject: [PATCH 3/5] add example programs --- doc/specs/stdlib_system.md | 28 ++--------------------- example/system/CMakeLists.txt | 2 ++ example/system/example_get_runtime_os.f90 | 9 ++++++++ example/system/example_os_type.f90 | 12 ++++++++++ 4 files changed, 25 insertions(+), 26 deletions(-) create mode 100644 example/system/example_get_runtime_os.f90 create mode 100644 example/system/example_os_type.f90 diff --git a/doc/specs/stdlib_system.md b/doc/specs/stdlib_system.md index 879a7d51e..3dbe434fe 100644 --- a/doc/specs/stdlib_system.md +++ b/doc/specs/stdlib_system.md @@ -376,19 +376,7 @@ Returns one of the `integer` `OS_*` parameters representing the OS type, from th ### Example ```fortran -program example_os_detection - use stdlib_system, only: OS_TYPE, get_runtime_os - implicit none - integer :: os_type_cached, os_type_runtime - - ! Cached OS detection - os_type_cached = OS_TYPE() - print *, "Cached OS Type: ", os_type_cached - - ! Runtime OS detection (full inspection) - os_type_runtime = get_runtime_os() - print *, "Runtime OS Type: ", os_type_runtime -end program example_os_detection +{!example/system/example_get_runtime_os.f90!} ``` --- @@ -427,17 +415,5 @@ Returns one of the `integer` `OS_*` parameters representing the OS type, from th ### Example ```fortran -program example_os_detection - use stdlib_system, only: OS_TYPE, get_runtime_os - implicit none - integer :: os_type_cached, os_type_runtime - - ! Cached OS detection - os_type_cached = OS_TYPE() - print *, "Cached OS Type: ", os_type_cached - - ! Runtime OS detection (full inspection) - os_type_runtime = get_runtime_os() - print *, "Runtime OS Type: ", os_type_runtime -end program example_os_detection +{!example/system/example_os_type.f90!} ``` diff --git a/example/system/CMakeLists.txt b/example/system/CMakeLists.txt index 5b4ef4054..f5518b74b 100644 --- a/example/system/CMakeLists.txt +++ b/example/system/CMakeLists.txt @@ -1,3 +1,5 @@ +ADD_EXAMPLE(get_runtime_os) +ADD_EXAMPLE(os_type) ADD_EXAMPLE(process_1) ADD_EXAMPLE(process_2) ADD_EXAMPLE(process_3) diff --git a/example/system/example_get_runtime_os.f90 b/example/system/example_get_runtime_os.f90 new file mode 100644 index 000000000..685f96d72 --- /dev/null +++ b/example/system/example_get_runtime_os.f90 @@ -0,0 +1,9 @@ +! Demonstrate usage of (non-cached) runtime OS query +program example_get_runtime_os + use stdlib_system, only: OS_NAME, get_runtime_os + implicit none + + ! Runtime OS detection (full inspection) + print *, "Runtime OS Type: ", OS_NAME(get_runtime_os()) + +end program example_get_runtime_os diff --git a/example/system/example_os_type.f90 b/example/system/example_os_type.f90 new file mode 100644 index 000000000..d3160827e --- /dev/null +++ b/example/system/example_os_type.f90 @@ -0,0 +1,12 @@ +! Demonstrate OS detection +program example_os_type + use stdlib_system, only: OS_TYPE, OS_NAME + implicit none + + integer :: current_os + + ! Cached OS detection + current_os = OS_TYPE() + print *, "Current OS Type: ", OS_NAME(current_os) + +end program example_os_type From 59da99849bc87a311435eac9be5698f5559f0975 Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Mon, 3 Mar 2025 08:59:18 +0100 Subject: [PATCH 4/5] add tests --- test/system/CMakeLists.txt | 1 + test/system/test_os.f90 | 70 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 test/system/test_os.f90 diff --git a/test/system/CMakeLists.txt b/test/system/CMakeLists.txt index 7dcc8060b..9434abeee 100644 --- a/test/system/CMakeLists.txt +++ b/test/system/CMakeLists.txt @@ -1,2 +1,3 @@ +ADDTEST(os) ADDTEST(sleep) ADDTEST(subprocess) diff --git a/test/system/test_os.f90 b/test/system/test_os.f90 new file mode 100644 index 000000000..836ea9893 --- /dev/null +++ b/test/system/test_os.f90 @@ -0,0 +1,70 @@ +module test_os + use testdrive, only : new_unittest, unittest_type, error_type, check, skip_test + use stdlib_system, only: get_runtime_os, OS_WINDOWS, OS_UNKNOWN, OS_TYPE, is_windows + + implicit none + +contains + + !> Collect all exported unit tests + subroutine collect_suite(testsuite) + !> Collection of tests + type(unittest_type), allocatable, intent(out) :: testsuite(:) + + testsuite = [ & + new_unittest('test_get_runtime_os', test_get_runtime_os), & + new_unittest('test_is_windows', test_is_windows) & + ] + end subroutine collect_suite + + subroutine test_get_runtime_os(error) + type(error_type), allocatable, intent(out) :: error + integer :: os + + !> Get current OS + os = get_runtime_os() + + call check(error, os /= OS_UNKNOWN, "running on an unknown/unsupported OS") + + end subroutine test_get_runtime_os + + !> If running on Windows (_WIN32 macro is defined), test that the appropriate OS flag is returned + subroutine test_is_windows(error) + type(error_type), allocatable, intent(out) :: error + integer :: os_cached, os_runtime + + call check(error, OS_TYPE()==OS_WINDOWS .eqv. is_windows(), & + "Cached OS type does not match _WIN32 macro presence") + + end subroutine test_is_windows + + +end module test_os + +program tester + use, intrinsic :: iso_fortran_env, only : error_unit + use testdrive, only : run_testsuite, new_testsuite, testsuite_type + use test_os, only : collect_suite + + implicit none + + integer :: stat, is + type(testsuite_type), allocatable :: testsuites(:) + character(len=*), parameter :: fmt = '("#", *(1x, a))' + + stat = 0 + + testsuites = [ & + new_testsuite("os", collect_suite) & + ] + + do is = 1, size(testsuites) + write(error_unit, fmt) "Testing:", testsuites(is)%name + call run_testsuite(testsuites(is)%collect, error_unit, stat) + end do + + if (stat > 0) then + write(error_unit, '(i0, 1x, a)') stat, "test(s) failed!" + error stop + end if +end program From fbaacc92e07db97cb1dac4d67b4ff0c8f3575fde Mon Sep 17 00:00:00 2001 From: Federico Perini Date: Mon, 3 Mar 2025 18:46:20 +0100 Subject: [PATCH 5/5] use `if` cascade --- src/stdlib_system.F90 | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/stdlib_system.F90 b/src/stdlib_system.F90 index 0c62b088e..576f72273 100644 --- a/src/stdlib_system.F90 +++ b/src/stdlib_system.F90 @@ -523,40 +523,34 @@ integer function get_runtime_os() result(os) if (index(val, 'linux') > 0) then os = OS_LINUX return - end if ! macOS - if (index(val, 'darwin') > 0) then + elseif (index(val, 'darwin') > 0) then os = OS_MACOS return - end if ! Windows, MSYS, MinGW, Git Bash - if (index(val, 'win') > 0 .or. index(val, 'msys') > 0) then + elseif (index(val, 'win') > 0 .or. index(val, 'msys') > 0) then os = OS_WINDOWS return - end if ! Cygwin - if (index(val, 'cygwin') > 0) then + elseif (index(val, 'cygwin') > 0) then os = OS_CYGWIN return - end if ! Solaris, OpenIndiana, ... - if (index(val, 'SunOS') > 0 .or. index(val, 'solaris') > 0) then + elseif (index(val, 'SunOS') > 0 .or. index(val, 'solaris') > 0) then os = OS_SOLARIS return - end if ! FreeBSD - if (index(val, 'FreeBSD') > 0 .or. index(val, 'freebsd') > 0) then + elseif (index(val, 'FreeBSD') > 0 .or. index(val, 'freebsd') > 0) then os = OS_FREEBSD return - end if ! OpenBSD - if (index(val, 'OpenBSD') > 0 .or. index(val, 'openbsd') > 0) then + elseif (index(val, 'OpenBSD') > 0 .or. index(val, 'openbsd') > 0) then os = OS_OPENBSD return end if @@ -593,6 +587,7 @@ integer function get_runtime_os() result(os) os = OS_FREEBSD return end if + end function get_runtime_os !> Retrieves the cached OS type for minimal runtime overhead.